|
//------------------------------------------------------------------------------
// <copyright file="HttpResponse.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//------------------------------------------------------------------------------
/*
* Response intrinsic
*/
namespace System.Web {
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Runtime.Serialization;
using System.IO;
using System.Collections;
using System.Collections.Specialized;
using System.Globalization;
using System.Web.Util;
using System.Web.Hosting;
using System.Web.Caching;
using System.Web.Configuration;
using System.Web.Routing;
using System.Web.UI;
using System.Configuration;
using System.Security.Permissions;
using System.Web.Management;
using System.Diagnostics.CodeAnalysis;
/// <devdoc>
/// <para>Used in HttpResponse.WriteSubstitution.</para>
/// </devdoc>
public delegate String HttpResponseSubstitutionCallback(HttpContext context);
/// <devdoc>
/// <para> Enables type-safe server to browser communication. Used to
/// send Http output to a client.</para>
/// </devdoc>
public sealed class HttpResponse {
private HttpWorkerRequest _wr; // some response have HttpWorkerRequest
private HttpContext _context; // context
private HttpWriter _httpWriter; // and HttpWriter
private TextWriter _writer; // others just have Writer
private HttpHeaderCollection _headers; // response header collection (IIS7+)
private bool _headersWritten;
private bool _completed; // after final flush
private bool _ended; // after response.end or execute url
private bool _endRequiresObservation; // whether there was a pending call to Response.End that requires observation
private bool _flushing;
private bool _clientDisconnected;
private bool _filteringCompleted;
private bool _closeConnectionAfterError;
// simple properties
private int _statusCode = 200;
private String _statusDescription;
private bool _bufferOutput = true;
private String _contentType = "text/html";
private String _charSet;
private bool _customCharSet;
private bool _contentLengthSet;
private String _redirectLocation;
private bool _redirectLocationSet;
private Encoding _encoding;
private Encoder _encoder; // cached encoder for the encoding
private Encoding _headerEncoding; // encoding for response headers, default utf-8
private bool _cacheControlHeaderAdded;
private HttpCachePolicy _cachePolicy;
private ArrayList _cacheHeaders;
private bool _suppressHeaders;
private bool _suppressContentSet;
private bool _suppressContent;
private string _appPathModifier;
private bool _isRequestBeingRedirected;
private bool _useAdaptiveError;
private bool _handlerHeadersGenerated;
private bool _sendCacheControlHeader;
// complex properties
private ArrayList _customHeaders;
private HttpCookieCollection _cookies;
#pragma warning disable 0649
private ResponseDependencyList _fileDependencyList;
private ResponseDependencyList _virtualPathDependencyList;
private ResponseDependencyList _cacheItemDependencyList;
#pragma warning restore 0649
private CacheDependency[] _userAddedDependencies;
private CacheDependency _cacheDependencyForResponse;
private ErrorFormatter _overrideErrorFormatter;
// cache properties
int _expiresInMinutes;
bool _expiresInMinutesSet;
DateTime _expiresAbsolute;
bool _expiresAbsoluteSet;
string _cacheControl;
private bool _statusSet;
private int _subStatusCode;
private bool _versionHeaderSent;
// These flags for content-type are only used in integrated mode.
// DevDivBugs 146983: Content-Type should not be sent when the resonse buffers are empty
// DevDivBugs 195148: need to send Content-Type when the handler is managed and the response buffers are non-empty
// Dev10 750934: need to send Content-Type when explicitly set by managed caller
private bool _contentTypeSetByManagedCaller;
private bool _contentTypeSetByManagedHandler;
// chunking
bool _transferEncodingSet;
bool _chunked;
// OnSendingHeaders pseudo-event
private SubscriptionQueue<Action<HttpContext>> _onSendingHeadersSubscriptionQueue = new SubscriptionQueue<Action<HttpContext>>();
// mobile redirect properties
internal static readonly String RedirectQueryStringVariable = "__redir";
internal static readonly String RedirectQueryStringValue = "1";
internal static readonly String RedirectQueryStringAssignment = RedirectQueryStringVariable + "=" + RedirectQueryStringValue;
private static readonly String _redirectQueryString = "?" + RedirectQueryStringAssignment;
private static readonly String _redirectQueryStringInline = RedirectQueryStringAssignment + "&";
internal static event EventHandler Redirecting;
internal HttpContext Context {
get { return _context; }
set { _context = value; }
}
internal HttpRequest Request {
get {
if (_context == null)
return null;
return _context.Request;
}
}
/*
* Internal package visible constructor to create responses that
* have HttpWorkerRequest
*
* @param wr Worker Request
*/
internal HttpResponse(HttpWorkerRequest wr, HttpContext context) {
_wr = wr;
_context = context;
// HttpWriter is created in InitResponseWriter
}
// Public constructor for responses that go to an arbitrary writer
// Initializes a new instance of the <see langword='HttpResponse'/> class.</para>
public HttpResponse(TextWriter writer) {
_wr = null;
_httpWriter = null;
_writer = writer;
}
private bool UsingHttpWriter {
get {
return (_httpWriter != null && _writer == _httpWriter);
}
}
internal void SetAllocatorProvider(IAllocatorProvider allocator) {
if (_httpWriter != null) {
_httpWriter.AllocatorProvider = allocator;
}
}
/*
* Cleanup code
*/
internal void Dispose() {
// recycle buffers
if (_httpWriter != null)
_httpWriter.RecycleBuffers();
// recycle dependencies
if (_cacheDependencyForResponse != null) {
_cacheDependencyForResponse.Dispose();
_cacheDependencyForResponse = null;
}
if (_userAddedDependencies != null) {
foreach (CacheDependency dep in _userAddedDependencies) {
dep.Dispose();
}
_userAddedDependencies = null;
}
}
internal void InitResponseWriter() {
if (_httpWriter == null) {
_httpWriter = new HttpWriter(this);
_writer = _httpWriter;
}
}
//
// Private helper methods
//
private void AppendHeader(HttpResponseHeader h) {
if (_customHeaders == null)
_customHeaders = new ArrayList();
_customHeaders.Add(h);
if (_cachePolicy != null && StringUtil.EqualsIgnoreCase("Set-Cookie", h.Name)) {
_cachePolicy.SetHasSetCookieHeader();
}
}
public bool HeadersWritten {
get { return _headersWritten; }
internal set { _headersWritten = value; }
}
internal ArrayList GenerateResponseHeadersIntegrated(bool forCache) {
ArrayList headers = new ArrayList();
HttpHeaderCollection responseHeaders = Headers as HttpHeaderCollection;
int headerId = 0;
// copy all current response headers
foreach(String key in responseHeaders)
{
// skip certain headers that the cache does not cache
// this is based on the cache headers saved separately in AppendHeader
// and not generated in GenerateResponseHeaders in ISAPI mode
headerId = HttpWorkerRequest.GetKnownResponseHeaderIndex(key);
if (headerId >= 0 && forCache &&
(headerId == HttpWorkerRequest.HeaderServer ||
headerId == HttpWorkerRequest.HeaderSetCookie ||
headerId == HttpWorkerRequest.HeaderCacheControl ||
headerId == HttpWorkerRequest.HeaderExpires ||
headerId == HttpWorkerRequest.HeaderLastModified ||
headerId == HttpWorkerRequest.HeaderEtag ||
headerId == HttpWorkerRequest.HeaderVary)) {
continue;
}
if ( headerId >= 0 ) {
headers.Add(new HttpResponseHeader(headerId, responseHeaders[key]));
}
else {
headers.Add(new HttpResponseHeader(key, responseHeaders[key]));
}
}
return headers;
}
internal void GenerateResponseHeadersForCookies()
{
if (_cookies == null || (_cookies.Count == 0 && !_cookies.Changed))
return; // no cookies exist
HttpHeaderCollection headers = Headers as HttpHeaderCollection;
HttpResponseHeader cookieHeader = null;
HttpCookie cookie = null;
bool needToReset = false;
// Go through all cookies, and check whether any have been added
// or changed. If a cookie was added, we can simply generate a new
// set cookie header for it. If the cookie collection has been
// changed (cleared or cookies removed), or an existing cookie was
// changed, we have to regenerate all Set-Cookie headers due to an IIS
// limitation that prevents us from being able to delete specific
// Set-Cookie headers for items that changed.
if (!_cookies.Changed)
{
for(int c = 0; c < _cookies.Count; c++)
{
cookie = _cookies[c];
if (cookie.Added) {
bool setHeader = true;
if (AppSettings.AvoidDuplicatedSetCookie) {
if(!cookie.IsInResponseHeader) {
cookie.IsInResponseHeader = true;
}
else {
setHeader = false;
}
}
if(setHeader) {
// if a cookie was added, we generate a Set-Cookie header for it
cookieHeader = cookie.GetSetCookieHeader(_context);
headers.SetHeader(cookieHeader.Name, cookieHeader.Value, false);
}
cookie.Added = false;
cookie.Changed = false;
}
else if (cookie.Changed) {
// if a cookie has changed, we need to clear all cookie
// headers and re-write them all since we cant delete
// specific existing cookies
needToReset = true;
break;
}
}
}
if (_cookies.Changed || needToReset)
{
// delete all set cookie headers
headers.Remove("Set-Cookie");
// write all the cookies again
for(int c = 0; c < _cookies.Count; c++)
{
// generate a Set-Cookie header for each cookie
cookie = _cookies[c];
cookieHeader = cookie.GetSetCookieHeader(_context);
headers.SetHeader(cookieHeader.Name, cookieHeader.Value, false);
cookie.Added = false;
cookie.Changed = false;
if(AppSettings.AvoidDuplicatedSetCookie) {
cookie.IsInResponseHeader = true;
}
}
_cookies.Changed = false;
}
}
internal void GenerateResponseHeadersForHandler()
{
if ( !(_wr is IIS7WorkerRequest) ) {
return;
}
String versionHeader = null;
// Generate the default headers associated with an ASP.NET handler
if (!_headersWritten && !_handlerHeadersGenerated) {
try {
// The "sendCacheControlHeader" is default to true, but a false setting in either
// the <httpRuntime> section (legacy) or the <outputCache> section (current) will disable
// sending of that header.
RuntimeConfig config = RuntimeConfig.GetLKGConfig(_context);
HttpRuntimeSection runtimeConfig = config.HttpRuntime;
if (runtimeConfig != null) {
versionHeader = runtimeConfig.VersionHeader;
_sendCacheControlHeader = runtimeConfig.SendCacheControlHeader;
}
OutputCacheSection outputCacheConfig = config.OutputCache;
if (outputCacheConfig != null) {
_sendCacheControlHeader &= outputCacheConfig.SendCacheControlHeader;
}
// DevDiv #406078: Need programmatic way of disabling "Cache-Control: private" response header.
if (SuppressDefaultCacheControlHeader) {
_sendCacheControlHeader = false;
}
// Ensure that cacheability is set to cache-control: private
// if it is not explicitly set
if (_sendCacheControlHeader && !_cacheControlHeaderAdded) {
Headers.Set("Cache-Control", "private");
}
// set the version header
if (!String.IsNullOrEmpty(versionHeader)) {
Headers.Set("X-AspNet-Version", versionHeader);
}
// Force content-type generation
_contentTypeSetByManagedHandler = true;
}
finally {
_handlerHeadersGenerated = true;
}
}
}
internal ArrayList GenerateResponseHeaders(bool forCache) {
ArrayList headers = new ArrayList();
bool sendCacheControlHeader = HttpRuntimeSection.DefaultSendCacheControlHeader;
// ASP.NET version header
if (!forCache ) {
if (!_versionHeaderSent) {
String versionHeader = null;
// The "sendCacheControlHeader" is default to true, but a false setting in either
// the <httpRuntime> section (legacy) or the <outputCache> section (current) will disable
// sending of that header.
RuntimeConfig config = RuntimeConfig.GetLKGConfig(_context);
HttpRuntimeSection runtimeConfig = config.HttpRuntime;
if (runtimeConfig != null) {
versionHeader = runtimeConfig.VersionHeader;
sendCacheControlHeader = runtimeConfig.SendCacheControlHeader;
}
OutputCacheSection outputCacheConfig = config.OutputCache;
if (outputCacheConfig != null) {
sendCacheControlHeader &= outputCacheConfig.SendCacheControlHeader;
}
if (!String.IsNullOrEmpty(versionHeader)) {
headers.Add(new HttpResponseHeader("X-AspNet-Version", versionHeader));
}
_versionHeaderSent = true;
}
}
// custom headers
if (_customHeaders != null) {
int numCustomHeaders = _customHeaders.Count;
for (int i = 0; i < numCustomHeaders; i++)
headers.Add(_customHeaders[i]);
}
// location of redirect
if (_redirectLocation != null) {
headers.Add(new HttpResponseHeader(HttpWorkerRequest.HeaderLocation, _redirectLocation));
}
// don't include headers that the cache changes or omits on a cache hit
if (!forCache) {
// cookies
if (_cookies != null) {
int numCookies = _cookies.Count;
for (int i = 0; i < numCookies; i++) {
headers.Add(_cookies[i].GetSetCookieHeader(Context));
}
}
// cache policy
if (_cachePolicy != null && _cachePolicy.IsModified()) {
_cachePolicy.GetHeaders(headers, this);
}
else {
if (_cacheHeaders != null) {
headers.AddRange(_cacheHeaders);
}
/*
* Ensure that cacheability is set to cache-control: private
* if it is not explicitly set.
*/
if (!_cacheControlHeaderAdded && sendCacheControlHeader) {
headers.Add(new HttpResponseHeader(HttpWorkerRequest.HeaderCacheControl, "private"));
}
}
}
//
// content type
//
if ( _statusCode != 204 && _contentType != null) {
String contentType = AppendCharSetToContentType( _contentType );
headers.Add(new HttpResponseHeader(HttpWorkerRequest.HeaderContentType, contentType));
}
// done
return headers;
}
internal string AppendCharSetToContentType(string contentType)
{
String newContentType = contentType;
// charset=xxx logic -- append if
// not there already and
// custom set or response encoding used by http writer to convert bytes to chars
if (_customCharSet || (_httpWriter != null && _httpWriter.ResponseEncodingUsed)) {
if (contentType.IndexOf("charset=", StringComparison.Ordinal) < 0) {
String charset = Charset;
if (charset.Length > 0) { // not suppressed
newContentType = contentType + "; charset=" + charset;
}
}
}
return newContentType;
}
internal bool UseAdaptiveError {
get {
return _useAdaptiveError;
}
set {
_useAdaptiveError = value;
}
}
private void WriteHeaders() {
if (_wr == null)
return;
// Fire pre-send headers event
if (_context != null && _context.ApplicationInstance != null) {
_context.ApplicationInstance.RaiseOnPreSendRequestHeaders();
}
// status
// VSWhidbey 270635: We need to reset the status code for mobile devices.
if (UseAdaptiveError) {
// VSWhidbey 288054: We should change the status code for cases
// that cannot be handled by mobile devices
// 4xx for Client Error and 5xx for Server Error in HTTP spec
int statusCode = StatusCode;
if (statusCode >= 400 && statusCode < 600) {
this.StatusCode = 200;
}
}
// generate headers before we touch the WorkerRequest since header generation might fail,
// and we don't want to have touched the WR if this happens
ArrayList headers = GenerateResponseHeaders(false);
_wr.SendStatus(this.StatusCode, this.StatusDescription);
// headers encoding
// unicode messes up the response badly
Debug.Assert(!this.HeaderEncoding.Equals(Encoding.Unicode));
_wr.SetHeaderEncoding(this.HeaderEncoding);
// headers
HttpResponseHeader header = null;
int n = (headers != null) ? headers.Count : 0;
for (int i = 0; i < n; i++)
{
header = headers[i] as HttpResponseHeader;
header.Send(_wr);
}
}
internal int GetBufferedLength() {
// if length is greater than Int32.MaxValue, Convert.ToInt32 will throw.
// This is okay until we support large response sizes
return (_httpWriter != null) ? Convert.ToInt32(_httpWriter.GetBufferedLength()) : 0;
}
private static byte[] s_chunkSuffix = new byte[2] { (byte)'\r', (byte)'\n'};
private static byte[] s_chunkEnd = new byte[5] { (byte)'0', (byte)'\r', (byte)'\n', (byte)'\r', (byte)'\n'};
private void Flush(bool finalFlush, bool async = false) {
// Already completed or inside Flush?
if (_completed || _flushing)
return;
// Special case for non HTTP Writer
if (_httpWriter == null) {
_writer.Flush();
return;
}
// Avoid recursive flushes
_flushing = true;
bool needToClearBuffers = false;
try {
IIS7WorkerRequest iis7WorkerRequest = _wr as IIS7WorkerRequest;
if (iis7WorkerRequest != null) {
// generate the handler headers if flushing
GenerateResponseHeadersForHandler();
// Push buffers across to native side and explicitly flush.
// IIS7 handles the chunking as necessary so we can omit that logic
UpdateNativeResponse(true /*sendHeaders*/);
if (!async) {
try {
// force a synchronous send
iis7WorkerRequest.ExplicitFlush();
}
finally {
// always set after flush, successful or not
_headersWritten = true;
}
}
return;
}
long bufferedLength = 0;
//
// Headers
//
if (!_headersWritten) {
if (!_suppressHeaders && !_clientDisconnected) {
EnsureSessionStateIfNecessary();
if (finalFlush) {
bufferedLength = _httpWriter.GetBufferedLength();
// suppress content-type for empty responses
if (!_contentLengthSet && bufferedLength == 0 && _httpWriter != null)
_contentType = null;
SuppressCachingCookiesIfNecessary();
// generate response headers
WriteHeaders();
// recalculate as sending headers might change it (PreSendHeaders)
bufferedLength = _httpWriter.GetBufferedLength();
// Calculate content-length if not set explicitely
// WOS #1380818: Content-Length should not be set for response with 304 status (HTTP.SYS doesn't, and HTTP 1.1 spec implies it)
if (!_contentLengthSet && _statusCode != 304)
_wr.SendCalculatedContentLength(bufferedLength);
}
else {
// Check if need chunking for HTTP/1.1
if (!_contentLengthSet && !_transferEncodingSet && _statusCode == 200) {
String protocol = _wr.GetHttpVersion();
if (protocol != null && protocol.Equals("HTTP/1.1")) {
AppendHeader(new HttpResponseHeader(HttpWorkerRequest.HeaderTransferEncoding, "chunked"));
_chunked = true;
}
bufferedLength = _httpWriter.GetBufferedLength();
}
WriteHeaders();
}
}
_headersWritten = true;
}
else {
bufferedLength = _httpWriter.GetBufferedLength();
}
//
// Filter and recalculate length if not done already
//
if (!_filteringCompleted) {
_httpWriter.Filter(false);
bufferedLength = _httpWriter.GetBufferedLength();
}
//
// Content
//
// suppress HEAD content unless overriden
if (!_suppressContentSet && Request != null && Request.HttpVerb == HttpVerb.HEAD)
_suppressContent = true;
if (_suppressContent || _ended) {
_httpWriter.ClearBuffers();
bufferedLength = 0;
}
if (!_clientDisconnected) {
// Fire pre-send request event
if (_context != null && _context.ApplicationInstance != null)
_context.ApplicationInstance.RaiseOnPreSendRequestContent();
if (_chunked) {
if (bufferedLength > 0) {
byte[] chunkPrefix = Encoding.ASCII.GetBytes(Convert.ToString(bufferedLength, 16) + "\r\n");
_wr.SendResponseFromMemory(chunkPrefix, chunkPrefix.Length);
_httpWriter.Send(_wr);
_wr.SendResponseFromMemory(s_chunkSuffix, s_chunkSuffix.Length);
}
if (finalFlush)
_wr.SendResponseFromMemory(s_chunkEnd, s_chunkEnd.Length);
}
else {
_httpWriter.Send(_wr);
}
if (!async) {
needToClearBuffers = !finalFlush;
_wr.FlushResponse(finalFlush);
}
_wr.UpdateResponseCounters(finalFlush, (int)bufferedLength);
}
}
finally {
_flushing = false;
// Remember if completed
if (finalFlush && _headersWritten)
_completed = true;
// clear buffers even if FlushResponse throws
if (needToClearBuffers)
_httpWriter.ClearBuffers();
}
}
internal void FinalFlushAtTheEndOfRequestProcessing() {
FinalFlushAtTheEndOfRequestProcessing(false);
}
internal void FinalFlushAtTheEndOfRequestProcessing(bool needPipelineCompletion) {
Flush(true);
}
// Returns true if the HttpWorkerRequest supports asynchronous flush; otherwise false.
public bool SupportsAsyncFlush {
get {
return (_wr != null && _wr.SupportsAsyncFlush);
}
}
// Sends the currently buffered response to the client. If the underlying HttpWorkerRequest
// supports asynchronous flush and this method is called from an asynchronous module event
// or asynchronous handler, then the send will be performed asynchronously. Otherwise the
// implementation resorts to a synchronous flush. The HttpResponse.SupportsAsyncFlush property
// returns the value of HttpWorkerRequest.SupportsAsyncFlush. Asynchronous flush is supported
// for IIS 6.0 and higher.
public IAsyncResult BeginFlush(AsyncCallback callback, Object state) {
if (_completed)
throw new HttpException(SR.GetString(SR.Cannot_flush_completed_response));
// perform async flush if it is supported
if (_wr != null && _wr.SupportsAsyncFlush && !_context.IsInCancellablePeriod) {
Flush(false, true);
return _wr.BeginFlush(callback, state);
}
// perform a sync flush since async is not supported
FlushAsyncResult ar = new FlushAsyncResult(callback, state);
try {
Flush(false);
}
catch(Exception e) {
ar.SetError(e);
}
ar.Complete(0, HResults.S_OK, IntPtr.Zero, synchronous: true);
return ar;
}
// Finish an asynchronous flush.
public void EndFlush(IAsyncResult asyncResult) {
// finish async flush if it is supported
if (_wr != null && _wr.SupportsAsyncFlush && !_context.IsInCancellablePeriod) {
// integrated mode doesn't set this until after ExplicitFlush is called,
// but classic mode sets it after WriteHeaders is called
_headersWritten = true;
if (!(_wr is IIS7WorkerRequest)) {
_httpWriter.ClearBuffers();
}
_wr.EndFlush(asyncResult);
return;
}
// finish sync flush since async is not supported
if (asyncResult == null)
throw new ArgumentNullException("asyncResult");
FlushAsyncResult ar = asyncResult as FlushAsyncResult;
if (ar == null)
throw new ArgumentException(null, "asyncResult");
ar.ReleaseWaitHandleWhenSignaled();
if (ar.Error != null) {
ar.Error.Throw();
}
}
public Task FlushAsync() {
return Task.Factory.FromAsync(BeginFlush, EndFlush, state: null);
}
// WOS 1555777: kernel cache support
// If the response can be kernel cached, return the kernel cache key;
// otherwise return null. The kernel cache key is used to invalidate
// the entry if a dependency changes or the item is flushed from the
// managed cache for any reason.
internal String SetupKernelCaching(String originalCacheUrl) {
// don't kernel cache if we have a cookie header
if (_cookies != null && _cookies.Count != 0) {
_cachePolicy.SetHasSetCookieHeader();
return null;
}
bool enableKernelCacheForVaryByStar = IsKernelCacheEnabledForVaryByStar();
// check cache policy
if (!_cachePolicy.IsKernelCacheable(Request, enableKernelCacheForVaryByStar)) {
return null;
}
// check configuration if the kernel mode cache is enabled
HttpRuntimeSection runtimeConfig = RuntimeConfig.GetLKGConfig(_context).HttpRuntime;
if (runtimeConfig == null || !runtimeConfig.EnableKernelOutputCache) {
return null;
}
double seconds = (_cachePolicy.UtcGetAbsoluteExpiration() - DateTime.UtcNow).TotalSeconds;
if (seconds <= 0) {
return null;
}
int secondsToLive = seconds < Int32.MaxValue ? (int) seconds : Int32.MaxValue;
string kernelCacheUrl = _wr.SetupKernelCaching(secondsToLive, originalCacheUrl, enableKernelCacheForVaryByStar);
if (kernelCacheUrl != null) {
// Tell cache policy not to use max-age as kernel mode cache doesn't update it
_cachePolicy.SetNoMaxAgeInCacheControl();
}
return kernelCacheUrl;
}
/*
* Disable kernel caching for this response. If kernel caching is not supported, this method
* returns without performing any action.
*/
public void DisableKernelCache() {
if (_wr == null) {
return;
}
_wr.DisableKernelCache();
}
/*
* Disable IIS user-mode caching for this response. If IIS user-mode caching is not supported, this method
* returns without performing any action.
*/
public void DisableUserCache() {
if (_wr == null) {
return;
}
_wr.DisableUserCache();
}
private bool IsKernelCacheEnabledForVaryByStar()
{
OutputCacheSection outputCacheConfig = RuntimeConfig.GetAppConfig().OutputCache;
return (_cachePolicy.IsVaryByStar && outputCacheConfig.EnableKernelCacheForVaryByStar);
}
internal void FilterOutput() {
if(_filteringCompleted) {
return;
}
try {
if (UsingHttpWriter) {
IIS7WorkerRequest iis7WorkerRequest = _wr as IIS7WorkerRequest;
if (iis7WorkerRequest != null) {
_httpWriter.FilterIntegrated(true, iis7WorkerRequest);
}
else {
_httpWriter.Filter(true);
}
}
}
finally {
_filteringCompleted = true;
}
}
/// <devdoc>
/// Prevents any other writes to the Response
/// </devdoc>
internal void IgnoreFurtherWrites() {
if (UsingHttpWriter) {
_httpWriter.IgnoreFurtherWrites();
}
}
/*
* Is the entire response buffered so far
*/
internal bool IsBuffered() {
return !_headersWritten && UsingHttpWriter;
}
// Expose cookie collection to request
// Gets the HttpCookie collection sent by the current request.</para>
public HttpCookieCollection Cookies {
get {
if (_cookies == null)
_cookies = new HttpCookieCollection(this, false);
return _cookies;
}
}
// returns TRUE iff there is at least one response cookie marked Shareable = false
internal bool ContainsNonShareableCookies() {
if (_cookies != null) {
for (int i = 0; i < _cookies.Count; i++) {
if (!_cookies[i].Shareable) {
return true;
}
}
}
return false;
}
internal HttpCookieCollection GetCookiesNoCreate() {
return _cookies;
}
public NameValueCollection Headers {
get {
if ( !(_wr is IIS7WorkerRequest) ) {
throw new PlatformNotSupportedException(SR.GetString(SR.Requires_Iis_Integrated_Mode));
}
if (_headers == null) {
_headers = new HttpHeaderCollection(_wr, this, 16);
}
return _headers;
}
}
/*
* Add dependency on a file to the current response
*/
/// <devdoc>
/// <para>Adds dependency on a file to the current response.</para>
/// </devdoc>
public void AddFileDependency(String filename) {
_fileDependencyList.AddDependency(filename, "filename");
}
// Add dependency on a list of files to the current response
// Adds dependency on a group of files to the current response.
public void AddFileDependencies(ArrayList filenames) {
_fileDependencyList.AddDependencies(filenames, "filenames");
}
public void AddFileDependencies(string[] filenames) {
_fileDependencyList.AddDependencies(filenames, "filenames");
}
internal void AddVirtualPathDependencies(string[] virtualPaths) {
_virtualPathDependencyList.AddDependencies(virtualPaths, "virtualPaths", false, Request.Path);
}
internal void AddFileDependencies(string[] filenames, DateTime utcTime) {
_fileDependencyList.AddDependencies(filenames, "filenames", false, utcTime);
}
// Add dependency on another cache item to the response.
public void AddCacheItemDependency(string cacheKey) {
_cacheItemDependencyList.AddDependency(cacheKey, "cacheKey");
}
// Add dependency on a list of cache items to the response.
public void AddCacheItemDependencies(ArrayList cacheKeys) {
_cacheItemDependencyList.AddDependencies(cacheKeys, "cacheKeys");
}
public void AddCacheItemDependencies(string[] cacheKeys) {
_cacheItemDependencyList.AddDependencies(cacheKeys, "cacheKeys");
}
// Add dependency on one or more CacheDependency objects to the response
public void AddCacheDependency(params CacheDependency[] dependencies) {
if (dependencies == null) {
throw new ArgumentNullException("dependencies");
}
if (dependencies.Length == 0) {
return;
}
if (_cacheDependencyForResponse != null) {
throw new InvalidOperationException(SR.GetString(SR.Invalid_operation_cache_dependency));
}
if (_userAddedDependencies == null) {
// copy array argument contents so they can't be changed beneath us
_userAddedDependencies = (CacheDependency[]) dependencies.Clone();
}
else {
CacheDependency[] deps = new CacheDependency[_userAddedDependencies.Length + dependencies.Length];
int i = 0;
for (i = 0; i < _userAddedDependencies.Length; i++) {
deps[i] = _userAddedDependencies[i];
}
for (int j = 0; j < dependencies.Length; j++) {
deps[i + j] = dependencies[j];
}
_userAddedDependencies = deps;
}
Cache.SetDependencies(true);
}
public static void RemoveOutputCacheItem(string path) {
RemoveOutputCacheItem(path, null);
}
public static void RemoveOutputCacheItem(string path, string providerName) {
if (path == null)
throw new ArgumentNullException("path");
if (StringUtil.StringStartsWith(path, "\\\\") || path.IndexOf(':') >= 0 || !UrlPath.IsRooted(path))
throw new ArgumentException(SR.GetString(SR.Invalid_path_for_remove, path));
string key = OutputCacheModule.CreateOutputCachedItemKey(
path, HttpVerb.GET, null, null);
if (providerName == null) {
OutputCache.Remove(key, (HttpContext)null);
}
else {
OutputCache.RemoveFromProvider(key, providerName);
}
key = OutputCacheModule.CreateOutputCachedItemKey(
path, HttpVerb.POST, null, null);
if (providerName == null) {
OutputCache.Remove(key, (HttpContext)null);
}
else {
OutputCache.RemoveFromProvider(key, providerName);
}
}
// Check if there are file dependencies.
internal bool HasFileDependencies() {
return _fileDependencyList.HasDependencies();
}
// Check if there are item dependencies.
internal bool HasCacheItemDependencies() {
return _cacheItemDependencyList.HasDependencies();
}
internal CacheDependency CreateCacheDependencyForResponse() {
if (_cacheDependencyForResponse == null) {
CacheDependency dependency;
// N.B. - add file dependencies last so that we hit the file changes monitor
// just once.
dependency = _cacheItemDependencyList.CreateCacheDependency(CacheDependencyType.CacheItems, null);
dependency = _fileDependencyList.CreateCacheDependency(CacheDependencyType.Files, dependency);
dependency = _virtualPathDependencyList.CreateCacheDependency(CacheDependencyType.VirtualPaths, dependency);
// N.B. we add in the aggregate dependency here, and return it,
// so this function should only be called once, because the resulting
// dependency can only be added to the cache once
if (_userAddedDependencies != null) {
AggregateCacheDependency agg = new AggregateCacheDependency();
agg.Add(_userAddedDependencies);
if (dependency != null) {
agg.Add(dependency);
}
// clear it because we added them to the dependencies for the response
_userAddedDependencies = null;
_cacheDependencyForResponse = agg;
}
else {
_cacheDependencyForResponse = dependency;
}
}
return _cacheDependencyForResponse;
}
// Get response headers and content as HttpRawResponse
internal HttpRawResponse GetSnapshot() {
int statusCode = 200;
string statusDescription = null;
ArrayList headers = null;
ArrayList buffers = null;
bool hasSubstBlocks = false;
if (!IsBuffered())
throw new HttpException(SR.GetString(SR.Cannot_get_snapshot_if_not_buffered));
IIS7WorkerRequest iis7WorkerRequest = _wr as IIS7WorkerRequest;
// data
if (!_suppressContent) {
if (iis7WorkerRequest != null) {
buffers = _httpWriter.GetIntegratedSnapshot(out hasSubstBlocks, iis7WorkerRequest);
}
else {
buffers = _httpWriter.GetSnapshot(out hasSubstBlocks);
}
}
// headers (after data as the data has side effects (like charset, see ASURT 113202))
if (!_suppressHeaders) {
statusCode = _statusCode;
statusDescription = _statusDescription;
// In integrated pipeline, we need to use the current response headers
// from the response (these may have been generated by other handlers, etc)
// instead of the ASP.NET cached headers
if (iis7WorkerRequest != null) {
headers = GenerateResponseHeadersIntegrated(true);
}
else {
headers = GenerateResponseHeaders(true);
}
}
return new HttpRawResponse(statusCode, statusDescription, headers, buffers, hasSubstBlocks);
}
// Send saved response snapshot as the entire response
internal void UseSnapshot(HttpRawResponse rawResponse, bool sendBody) {
if (_headersWritten)
throw new HttpException(SR.GetString(SR.Cannot_use_snapshot_after_headers_sent));
if (_httpWriter == null)
throw new HttpException(SR.GetString(SR.Cannot_use_snapshot_for_TextWriter));
ClearAll();
// restore status
StatusCode = rawResponse.StatusCode;
StatusDescription = rawResponse.StatusDescription;
// restore headers
ArrayList headers = rawResponse.Headers;
int n = (headers != null) ? headers.Count : 0;
for (int i = 0; i < n; i++) {
HttpResponseHeader h = (HttpResponseHeader)(headers[i]);
this.AppendHeader(h.Name, h.Value);
}
// restore content
SetResponseBuffers(rawResponse.Buffers);
_suppressContent = !sendBody;
}
// set the response content bufffers
internal void SetResponseBuffers(ArrayList buffers) {
if (_httpWriter == null) {
throw new HttpException(SR.GetString(SR.Cannot_use_snapshot_for_TextWriter));
}
_httpWriter.UseSnapshot(buffers);
}
internal void CloseConnectionAfterError() {
_closeConnectionAfterError = true;
}
private void WriteErrorMessage(Exception e, bool dontShowSensitiveErrors) {
ErrorFormatter errorFormatter = null;
CultureInfo uiculture = null, savedUiculture = null;
bool needToRestoreUiculture = false;
if (_context.DynamicUICulture != null) {
// if the user set the culture dynamically use it
uiculture = _context.DynamicUICulture;
}
else {
// get the UI culture under which the error text must be created (use LKG to avoid errors while reporting error)
GlobalizationSection globConfig = RuntimeConfig.GetLKGConfig(_context).Globalization;
if ((globConfig != null) && (!String.IsNullOrEmpty(globConfig.UICulture))) {
try {
uiculture = HttpServerUtility.CreateReadOnlyCultureInfo(globConfig.UICulture);
}
catch {
}
}
}
// In Integrated mode, generate the necessary response headers for the error
GenerateResponseHeadersForHandler();
// set the UI culture
if (uiculture != null) {
savedUiculture = Thread.CurrentThread.CurrentUICulture;
Thread.CurrentThread.CurrentUICulture = uiculture;
needToRestoreUiculture = true;
}
try {
try {
// Try to get an error formatter
errorFormatter = GetErrorFormatter(e);
#if DBG
Debug.Trace("internal", "Error stack for " + Request.Path, e);
#endif
if (dontShowSensitiveErrors && !errorFormatter.CanBeShownToAllUsers)
errorFormatter = new GenericApplicationErrorFormatter(Request.IsLocal);
Debug.Trace("internal", "errorFormatter's type = " + errorFormatter.GetType());
if (ErrorFormatter.RequiresAdaptiveErrorReporting(Context)) {
_writer.Write(errorFormatter.GetAdaptiveErrorMessage(Context, dontShowSensitiveErrors));
}
else {
_writer.Write(errorFormatter.GetHtmlErrorMessage(dontShowSensitiveErrors));
// Write a stack dump in an HTML comment for debugging purposes
// Only show it for Asp permission medium or higher (ASURT 126373)
if (!dontShowSensitiveErrors &&
HttpRuntime.HasAspNetHostingPermission(AspNetHostingPermissionLevel.Medium)) {
_writer.Write("<!-- \r\n");
WriteExceptionStack(e);
_writer.Write("-->");
}
if (!dontShowSensitiveErrors && !Request.IsLocal ) {
_writer.Write("<!-- \r\n");
_writer.Write(SR.GetString(SR.Information_Disclosure_Warning));
_writer.Write("-->");
}
}
if (_closeConnectionAfterError) {
Flush();
Close();
}
}
finally {
// restore ui culture
if (needToRestoreUiculture)
Thread.CurrentThread.CurrentUICulture = savedUiculture;
}
}
catch { // Protect against exception filters
throw;
}
}
internal void SetOverrideErrorFormatter(ErrorFormatter errorFormatter) {
_overrideErrorFormatter = errorFormatter;
}
internal ErrorFormatter GetErrorFormatter(Exception e) {
ErrorFormatter errorFormatter = null;
if (_overrideErrorFormatter != null) {
return _overrideErrorFormatter;
}
// Try to get an error formatter
errorFormatter = HttpException.GetErrorFormatter(e);
if (errorFormatter == null) {
ConfigurationException ce = e as ConfigurationException;
if (ce != null && !String.IsNullOrEmpty(ce.Filename))
errorFormatter = new ConfigErrorFormatter(ce);
}
// If we couldn't get one, create one here
if (errorFormatter == null) {
// If it's a 404, use a special error page, otherwise, use a more
// generic one.
if (_statusCode == 404)
errorFormatter = new PageNotFoundErrorFormatter(Request.Path);
else if (_statusCode == 403)
errorFormatter = new PageForbiddenErrorFormatter(Request.Path);
else {
if (e is System.Security.SecurityException)
errorFormatter = new SecurityErrorFormatter(e);
else
errorFormatter = new UnhandledErrorFormatter(e);
}
}
// Show config source only on local request for security reasons
// Config file snippet may unintentionally reveal sensitive information (not related to the error)
ConfigErrorFormatter configErrorFormatter = errorFormatter as ConfigErrorFormatter;
if (configErrorFormatter != null) {
configErrorFormatter.AllowSourceCode = Request.IsLocal;
}
return errorFormatter;
}
private void WriteOneExceptionStack(Exception e) {
Exception subExcep = e.InnerException;
if (subExcep != null)
WriteOneExceptionStack(subExcep);
string title = "[" + e.GetType().Name + "]";
if (e.Message != null && e.Message.Length > 0)
title += ": " + HttpUtility.HtmlEncode(e.Message);
_writer.WriteLine(title);
if (e.StackTrace != null)
_writer.WriteLine(e.StackTrace);
}
private void WriteExceptionStack(Exception e) {
ConfigurationErrorsException errors = e as ConfigurationErrorsException;
if (errors == null) {
WriteOneExceptionStack(e);
}
else {
// Write the original exception to get the first error with
// a full stack trace
WriteOneExceptionStack(e);
// Write additional errors, which will contain truncated stacks
ICollection col = errors.Errors;
if (col.Count > 1) {
bool firstSkipped = false;
foreach (ConfigurationException ce in col) {
if (!firstSkipped) {
firstSkipped = true;
continue;
}
_writer.WriteLine("---");
WriteOneExceptionStack(ce);
}
}
}
}
internal void ReportRuntimeError(Exception e, bool canThrow, bool localExecute) {
CustomErrorsSection customErrorsSetting = null;
bool useCustomErrors = false;
int code = -1;
if (_completed)
return;
// always try to disable IIS custom errors when we send an error
if (_wr != null) {
_wr.TrySkipIisCustomErrors = true;
}
if (!localExecute) {
code = HttpException.GetHttpCodeForException(e);
// Don't raise event for 404. See VSWhidbey 124147.
if (code != 404) {
WebBaseEvent.RaiseRuntimeError(e, this);
}
// This cannot use the HttpContext.IsCustomErrorEnabled property, since it must call
// GetSettings() with the canThrow parameter.
customErrorsSetting = CustomErrorsSection.GetSettings(_context, canThrow);
if (customErrorsSetting != null)
useCustomErrors = customErrorsSetting.CustomErrorsEnabled(Request);
else
useCustomErrors = true;
}
if (!_headersWritten) {
// nothing sent yet - entire response
if (code == -1) {
code = HttpException.GetHttpCodeForException(e);
}
// change 401 to 500 in case the config is not to impersonate
if (code == 401 && !_context.IsClientImpersonationConfigured)
code = 500;
if (_context.TraceIsEnabled)
_context.Trace.StatusCode = code;
if (!localExecute && useCustomErrors) {
String redirect = (customErrorsSetting != null) ? customErrorsSetting.GetRedirectString(code) : null;
RedirectToErrorPageStatus redirectStatus = RedirectToErrorPage(redirect, customErrorsSetting.RedirectMode);
switch (redirectStatus) {
case RedirectToErrorPageStatus.Success:
// success - nothing to do
break;
case RedirectToErrorPageStatus.NotAttempted:
// if no redirect display generic error
ClearAll();
StatusCode = code;
WriteErrorMessage(e, dontShowSensitiveErrors: true);
break;
default:
// DevDiv #70492 - If we tried to display the custom error page but failed in doing so, we should display
// a generic error message instead of trying to display the original error. We have a compat switch on
// the <customErrors> element to control this behavior.
if (customErrorsSetting.AllowNestedErrors) {
// The user has set the compat switch to use the original (pre-bug fix) behavior.
goto case RedirectToErrorPageStatus.NotAttempted;
}
ClearAll();
StatusCode = 500;
HttpException dummyException = new HttpException();
dummyException.SetFormatter(new CustomErrorFailedErrorFormatter());
WriteErrorMessage(dummyException, dontShowSensitiveErrors: true);
break;
}
}
else {
ClearAll();
StatusCode = code;
WriteErrorMessage(e, dontShowSensitiveErrors: false);
}
}
else {
Clear();
if (_contentType != null && _contentType.Equals("text/html")) {
// in the middle of Html - break Html
Write("\r\n\r\n</pre></table></table></table></table></table>");
Write("</font></font></font></font></font>");
Write("</i></i></i></i></i></b></b></b></b></b></u></u></u></u></u>");
Write("<p> </p><hr>\r\n\r\n");
}
WriteErrorMessage(e, useCustomErrors);
}
}
internal void SynchronizeStatus(int statusCode, int subStatusCode, string description) {
_statusCode = statusCode;
_subStatusCode = subStatusCode;
_statusDescription = description;
}
internal void SynchronizeHeader(int knownHeaderIndex, string name, string value) {
HttpHeaderCollection headers = Headers as HttpHeaderCollection;
headers.SynchronizeHeader(name, value);
// unknown headers have an index < 0
if (knownHeaderIndex < 0) {
return;
}
bool fHeadersWritten = HeadersWritten;
HeadersWritten = false; // Turn off the warning for "Headers have been written and can not be set"
try {
switch (knownHeaderIndex) {
case HttpWorkerRequest.HeaderCacheControl:
_cacheControlHeaderAdded = true;
break;
case HttpWorkerRequest.HeaderContentType:
_contentType = value;
break;
case HttpWorkerRequest.HeaderLocation:
_redirectLocation = value;
_redirectLocationSet = false;
break;
case HttpWorkerRequest.HeaderSetCookie:
// If the header is Set-Cookie, update the corresponding
// cookie in the cookies collection
if (value != null) {
HttpCookie cookie = HttpRequest.CreateCookieFromString(value);
// do not write this cookie back to IIS
cookie.IsInResponseHeader = true;
Cookies.Set(cookie);
cookie.Changed = false;
cookie.Added = false;
}
break;
}
} finally {
HeadersWritten = fHeadersWritten;
}
}
internal void SyncStatusIntegrated() {
Debug.Assert(_wr is IIS7WorkerRequest, "_wr is IIS7WorkerRequest");
if (!_headersWritten && _statusSet) {
// For integrated pipeline, synchronize the status immediately so that the FREB log
// correctly indicates the module and notification that changed the status.
_wr.SendStatus(_statusCode, _subStatusCode, this.StatusDescription);
_statusSet = false;
}
}
// Public properties
// Http status code
// Gets or sets the HTTP status code of output returned to client.
public int StatusCode {
get {
return _statusCode;
}
set {
if (_headersWritten)
throw new HttpException(SR.GetString(SR.Cannot_set_status_after_headers_sent));
if (_statusCode != value) {
_statusCode = value;
_subStatusCode = 0;
_statusDescription = null;
_statusSet = true;
}
}
}
// the IIS sub status code
// since this doesn't get emitted in the protocol
// we won't send it through the worker request interface
// directly
public int SubStatusCode {
get {
if ( !(_wr is IIS7WorkerRequest) ) {
throw new PlatformNotSupportedException(SR.GetString(SR.Requires_Iis_Integrated_Mode));
}
return _subStatusCode;
}
set {
if ( !(_wr is IIS7WorkerRequest) ) {
throw new PlatformNotSupportedException(SR.GetString(SR.Requires_Iis_Integrated_Mode));
}
if (_headersWritten) {
throw new HttpException(SR.GetString(SR.Cannot_set_status_after_headers_sent));
}
_subStatusCode = value;
_statusSet = true;
}
}
// Allows setting both the status and the substatus individually. If not in IIS7 integrated mode,
// the substatus code is ignored so as not to throw an exception.
internal void SetStatusCode(int statusCode, int subStatus = -1) {
StatusCode = statusCode;
if (subStatus >= 0 && _wr is IIS7WorkerRequest) {
SubStatusCode = subStatus;
}
}
/*
* Http status description string
*/
// Http status description string
// Gets or sets the HTTP status string of output returned to the client.
public String StatusDescription {
get {
if (_statusDescription == null)
_statusDescription = HttpWorkerRequest.GetStatusDescription(_statusCode);
return _statusDescription;
}
set {
if (_headersWritten)
throw new HttpException(SR.GetString(SR.Cannot_set_status_after_headers_sent));
if (value != null && value.Length > 512) // ASURT 124743
throw new ArgumentOutOfRangeException("value");
_statusDescription = value;
_statusSet = true;
}
}
public bool TrySkipIisCustomErrors {
get {
if (_wr != null) {
return _wr.TrySkipIisCustomErrors;
}
return false;
}
set {
if (_wr != null) {
_wr.TrySkipIisCustomErrors = value;
}
}
}
/// <summary>
/// By default, the FormsAuthenticationModule hooks EndRequest and converts HTTP 401 status codes to
/// HTTP 302, redirecting to the login page. This isn't appropriate for certain classes of errors,
/// e.g. where authentication succeeded but authorization failed, or where the current request is
/// an AJAX or web service request. This property provides a way to suppress the redirect behavior
/// and send the original status code to the client.
/// </summary>
public bool SuppressFormsAuthenticationRedirect {
get;
set;
}
/// <summary>
/// By default, ASP.NET sends a "Cache-Control: private" response header unless an explicit cache
/// policy has been specified for this response. This property allows suppressing this default
/// response header on a per-request basis. It can still be suppressed for the entire application
/// by setting the appropriate value in <httpRuntime> or <outputCache>. See
/// http://msdn.microsoft.com/en-us/library/system.web.configuration.httpruntimesection.sendcachecontrolheader.aspx
/// for more information on those config elements.
/// </summary>
/// <remarks>
/// Developers should use caution when suppressing the default Cache-Control header, as proxies
/// and other intermediaries may treat responses without this header as cacheable by default.
/// This could lead to the inadvertent caching of sensitive information.
/// See RFC 2616, Sec. 13.4, for more information.
/// </remarks>
public bool SuppressDefaultCacheControlHeader {
get;
set;
}
// Flag indicating to buffer the output
// Gets or sets a value indicating whether HTTP output is buffered.
public bool BufferOutput {
get {
return _bufferOutput;
}
set {
if (_bufferOutput != value) {
_bufferOutput = value;
if (_httpWriter != null)
_httpWriter.UpdateResponseBuffering();
}
}
}
// Gets the Content-Encoding HTTP response header.
internal String GetHttpHeaderContentEncoding() {
string coding = null;
if (_wr is IIS7WorkerRequest) {
if (_headers != null) {
coding = _headers["Content-Encoding"];
}
}
else if (_customHeaders != null) {
int numCustomHeaders = _customHeaders.Count;
for (int i = 0; i < numCustomHeaders; i++) {
HttpResponseHeader h = (HttpResponseHeader)_customHeaders[i];
if (h.Name == "Content-Encoding") {
coding = h.Value;
break;
}
}
}
return coding;
}
/*
* Content-type
*/
/// <devdoc>
/// <para>Gets or sets the
/// HTTP MIME type of output.</para>
/// </devdoc>
public String ContentType {
get {
return _contentType;
}
set {
if (_headersWritten) {
// Don't throw if the new content type is the same as the current one
if (_contentType == value)
return;
throw new HttpException(SR.GetString(SR.Cannot_set_content_type_after_headers_sent));
}
_contentTypeSetByManagedCaller = true;
_contentType = value;
}
}
// Gets or sets the HTTP charset of output.
public String Charset {
get {
if (_charSet == null)
_charSet = ContentEncoding.WebName;
return _charSet;
}
set {
if (_headersWritten)
throw new HttpException(SR.GetString(SR.Cannot_set_content_type_after_headers_sent));
if (value != null)
_charSet = value;
else
_charSet = String.Empty; // to differentiate between not set (default) and empty chatset
_customCharSet = true;
}
}
// Content encoding for conversion
// Gets or sets the HTTP character set of output.
public Encoding ContentEncoding {
get {
if (_encoding == null) {
// use LKG config because Response.ContentEncoding is need to display [config] error
GlobalizationSection globConfig = RuntimeConfig.GetLKGConfig(_context).Globalization;
if (globConfig != null)
_encoding = globConfig.ResponseEncoding;
if (_encoding == null)
_encoding = Encoding.Default;
}
return _encoding;
}
set {
if (value == null)
throw new ArgumentNullException("value");
if (_encoding == null || !_encoding.Equals(value)) {
_encoding = value;
_encoder = null; // flush cached encoder
if (_httpWriter != null)
_httpWriter.UpdateResponseEncoding();
}
}
}
public Encoding HeaderEncoding {
get {
if (_headerEncoding == null) {
// use LKG config because Response.ContentEncoding is need to display [config] error
GlobalizationSection globConfig = RuntimeConfig.GetLKGConfig(_context).Globalization;
if (globConfig != null)
_headerEncoding = globConfig.ResponseHeaderEncoding;
// default to UTF-8 (also for Unicode as headers cannot be double byte encoded)
if (_headerEncoding == null || _headerEncoding.Equals(Encoding.Unicode))
_headerEncoding = Encoding.UTF8;
}
return _headerEncoding;
}
set {
if (value == null)
throw new ArgumentNullException("value");
if (value.Equals(Encoding.Unicode)) {
throw new HttpException(SR.GetString(SR.Invalid_header_encoding, value.WebName));
}
if (_headerEncoding == null || !_headerEncoding.Equals(value)) {
if (_headersWritten)
throw new HttpException(SR.GetString(SR.Cannot_set_header_encoding_after_headers_sent));
_headerEncoding = value;
}
}
}
// Encoder cached for the current encoding
internal Encoder ContentEncoder {
get {
if (_encoder == null) {
Encoding e = ContentEncoding;
_encoder = e.GetEncoder();
// enable best fit mapping accoding to config
// (doesn't apply to utf-8 which is the default, thus optimization)
if (!e.Equals(Encoding.UTF8)) {
bool enableBestFit = false;
GlobalizationSection globConfig = RuntimeConfig.GetLKGConfig(_context).Globalization;
if (globConfig != null) {
enableBestFit = globConfig.EnableBestFitResponseEncoding;
}
if (!enableBestFit) {
// setting 'fallback' disables best fit mapping
_encoder.Fallback = new EncoderReplacementFallback();
}
}
}
return _encoder;
}
}
// Cache policy
// Returns the caching semantics of the Web page (expiration time, privacy, vary clauses).
public HttpCachePolicy Cache {
get {
if (_cachePolicy == null) {
_cachePolicy = new HttpCachePolicy();
}
return _cachePolicy;
}
}
// Return whether or not we have cache policy. We don't want to create it in
// situations where we don't modify it.
internal bool HasCachePolicy {
get {
return _cachePolicy != null;
}
}
// Client connected flag
// Gets a value indicating whether the client is still connected to the server.
public bool IsClientConnected {
get {
if (_clientDisconnected)
return false;
if (_wr != null && !_wr.IsClientConnected()) {
_clientDisconnected = true;
return false;
}
return true;
}
}
/// <summary>
/// Returns a CancellationToken that is tripped when the client disconnects. This can be used
/// to listen for async disconnect notifications.
/// </summary>
/// <remarks>
/// This method requires that the application be hosted on IIS 7.5 or higher and that the
/// application pool be running the integrated mode pipeline.
///
/// Consumers should be aware of some restrictions when consuming this CancellationToken.
/// Failure to heed these warnings can lead to race conditions, deadlocks, or other
/// undefined behavior.
///
/// - This API is thread-safe. However, ASP.NET will dispose of the token object at the
/// end of the request. Consumers should exercise caution and ensure that they're not
/// calling into this API outside the bounds of a single request. This is similar to
/// the contract with BCL Task-returning methods which take a CancellationToken as a
/// parameter: the callee should not touch the CancellationToken after the returned
/// Task transitions to a terminal state.
///
/// - DO NOT wait on the CancellationToken.WaitHandle, as this defeats the purpose of an
/// async notification and can cause deadlocks.
///
/// - DO NOT call the CancellationToken.Register overloads which invoke the callback on
/// the original SynchronizationContext.
///
/// - DO NOT consume HttpContext or other non-thread-safe ASP.NET intrinsic objects from
/// within the callback provided to Register. Remember: the callback may be running
/// concurrently with other ASP.NET or application code.
///
/// - DO keep the callback methods short-running and non-blocking. Make every effort to
/// avoid throwing exceptions from within the callback methods.
///
/// - We do not guarantee that we will ever transition the token to a canceled state.
/// For example, if the request finishes without the client having disconnected, we
/// will dispose of this token as mentioned earlier without having first canceled it.
/// </remarks>
public CancellationToken ClientDisconnectedToken {
get {
IIS7WorkerRequest wr = _wr as IIS7WorkerRequest;
CancellationToken cancellationToken;
if (wr != null && wr.TryGetClientDisconnectedCancellationToken(out cancellationToken)) {
return cancellationToken;
}
else {
throw new PlatformNotSupportedException(SR.GetString(SR.Requires_Iis_75_Integrated));
}
}
}
public bool IsRequestBeingRedirected {
get {
return _isRequestBeingRedirected;
}
internal set {
_isRequestBeingRedirected = value;
}
}
/// <devdoc>
/// <para>Gets or Sets a redirection string (value of location resposne header) for redirect response.</para>
/// </devdoc>
public String RedirectLocation {
get { return _redirectLocation; }
set {
if (_headersWritten)
throw new HttpException(SR.GetString(SR.Cannot_append_header_after_headers_sent));
_redirectLocation = value;
_redirectLocationSet = true;
}
}
/*
* Disconnect client
*/
/// <devdoc>
/// <para>Closes the socket connection to a client.</para>
/// </devdoc>
public void Close() {
if (!_clientDisconnected && !_completed && _wr != null) {
_wr.CloseConnection();
_clientDisconnected = true;
}
}
// TextWriter object
// Enables custom output to the outgoing Http content body.
public TextWriter Output {
get { return _writer;}
set { _writer = value; }
}
internal TextWriter SwitchWriter(TextWriter writer) {
TextWriter oldWriter = _writer;
_writer = writer;
return oldWriter;
}
// Output stream
// Enables binary output to the outgoing Http content body.
public Stream OutputStream {
get {
if (!UsingHttpWriter)
throw new HttpException(SR.GetString(SR.OutputStream_NotAvail));
return _httpWriter.OutputStream;
}
}
// ASP classic compat
// Writes a string of binary characters to the HTTP output stream.
public void BinaryWrite(byte[] buffer) {
OutputStream.Write(buffer, 0, buffer.Length);
}
// Appends a PICS (Platform for Internet Content Selection) label HTTP header to the output stream.
public void Pics(String value) {
AppendHeader("PICS-Label", value);
}
// Filtering stream
// Specifies a wrapping filter object to modify HTTP entity body before transmission.
public Stream Filter {
get {
if (UsingHttpWriter)
return _httpWriter.GetCurrentFilter();
else
return null;
}
set {
if (UsingHttpWriter) {
_httpWriter.InstallFilter(value);
IIS7WorkerRequest iis7WorkerRequest = _wr as IIS7WorkerRequest;
if (iis7WorkerRequest != null) {
iis7WorkerRequest.ResponseFilterInstalled();
}
}
else
throw new HttpException(SR.GetString(SR.Filtering_not_allowed));
}
}
// Flag to suppress writing of content
// Gets or sets a value indicating that HTTP content will not be sent to client.
public bool SuppressContent {
get {
return _suppressContent;
}
set {
_suppressContent = value;
_suppressContentSet = true;
}
}
//
// Public methods
//
/*
* Add Http custom header
*
* @param name header name
* @param value header value
*/
/// <devdoc>
/// <para>Adds an HTTP
/// header to the output stream.</para>
/// </devdoc>
public void AppendHeader(String name, String value) {
bool isCacheHeader = false;
if (_headersWritten)
throw new HttpException(SR.GetString(SR.Cannot_append_header_after_headers_sent));
// some headers are stored separately or require special action
int knownHeaderIndex = HttpWorkerRequest.GetKnownResponseHeaderIndex(name);
switch (knownHeaderIndex) {
case HttpWorkerRequest.HeaderContentType:
ContentType = value;
return; // don't keep as custom header
case HttpWorkerRequest.HeaderContentLength:
_contentLengthSet = true;
break;
case HttpWorkerRequest.HeaderLocation:
RedirectLocation = value;
return; // don't keep as custom header
case HttpWorkerRequest.HeaderTransferEncoding:
_transferEncodingSet = true;
break;
case HttpWorkerRequest.HeaderCacheControl:
_cacheControlHeaderAdded = true;
goto case HttpWorkerRequest.HeaderExpires;
case HttpWorkerRequest.HeaderExpires:
case HttpWorkerRequest.HeaderLastModified:
case HttpWorkerRequest.HeaderEtag:
case HttpWorkerRequest.HeaderVary:
isCacheHeader = true;
break;
}
// In integrated mode, write the headers directly
if (_wr is IIS7WorkerRequest) {
Headers.Add(name, value);
}
else {
if (isCacheHeader)
{
// don't keep as custom header
if (_cacheHeaders == null) {
_cacheHeaders = new ArrayList();
}
_cacheHeaders.Add(new HttpResponseHeader(knownHeaderIndex, value));
return;
}
else {
HttpResponseHeader h;
if (knownHeaderIndex >= 0)
h = new HttpResponseHeader(knownHeaderIndex, value);
else
h = new HttpResponseHeader(name, value);
AppendHeader(h);
}
}
}
/// <internalonly/>
/// <devdoc>
/// <para>
/// Adds an HTTP
/// cookie to the output stream.
/// </para>
/// </devdoc>
public void AppendCookie(HttpCookie cookie) {
if (_headersWritten)
throw new HttpException(SR.GetString(SR.Cannot_append_cookie_after_headers_sent));
Cookies.AddCookie(cookie, true);
OnCookieAdd(cookie);
}
/// <internalonly/>
/// <devdoc>
/// </devdoc>
public void SetCookie(HttpCookie cookie) {
if (_headersWritten)
throw new HttpException(SR.GetString(SR.Cannot_append_cookie_after_headers_sent));
Cookies.AddCookie(cookie, false);
OnCookieCollectionChange();
}
internal void BeforeCookieCollectionChange() {
if (_headersWritten)
throw new HttpException(SR.GetString(SR.Cannot_modify_cookies_after_headers_sent));
}
internal void OnCookieAdd(HttpCookie cookie) {
// add to request's cookies as well
Request.AddResponseCookie(cookie);
}
internal void OnCookieCollectionChange() {
// synchronize with request cookie collection
Request.ResetCookies();
}
// Clear response headers
// Clears all headers from the buffer stream.
public void ClearHeaders() {
if (_headersWritten)
throw new HttpException(SR.GetString(SR.Cannot_clear_headers_after_headers_sent));
StatusCode = 200;
_subStatusCode = 0;
_statusDescription = null;
_contentType = "text/html";
_contentTypeSetByManagedCaller = false;
_charSet = null;
_customCharSet = false;
_contentLengthSet = false;
_redirectLocation = null;
_redirectLocationSet = false;
_isRequestBeingRedirected = false;
_customHeaders = null;
if (_headers != null) {
_headers.ClearInternal();
}
_transferEncodingSet = false;
_chunked = false;
if (_cookies != null) {
_cookies.Reset();
Request.ResetCookies();
}
if (_cachePolicy != null) {
_cachePolicy.Reset();
}
_cacheControlHeaderAdded = false;
_cacheHeaders = null;
_suppressHeaders = false;
_suppressContent = false;
_suppressContentSet = false;
_expiresInMinutes = 0;
_expiresInMinutesSet = false;
_expiresAbsolute = DateTime.MinValue;
_expiresAbsoluteSet = false;
_cacheControl = null;
IIS7WorkerRequest iis7WorkerRequest = _wr as IIS7WorkerRequest;
if (iis7WorkerRequest != null) {
// clear the native response as well
ClearNativeResponse(false, true, iis7WorkerRequest);
// DevDiv 162749:
// We need to regenerate Cache-Control: private only when the handler is managed and
// configuration has <outputCache sendCacheControlHeader="true" /> in system.web
// caching section.
if (_handlerHeadersGenerated && _sendCacheControlHeader) {
Headers.Set("Cache-Control", "private");
}
_handlerHeadersGenerated = false;
}
}
/// <devdoc>
/// <para>Clears all content output from the buffer stream.</para>
/// </devdoc>
public void ClearContent() {
Clear();
}
/*
* Clear response buffer and headers. (For ASP compat doesn't clear headers)
*/
/// <devdoc>
/// <para>Clears all headers and content output from the buffer stream.</para>
/// </devdoc>
public void Clear() {
if (UsingHttpWriter)
_httpWriter.ClearBuffers();
IIS7WorkerRequest iis7WorkerRequest = _wr as IIS7WorkerRequest;
if (iis7WorkerRequest != null) {
// clear the native response buffers too
ClearNativeResponse(true, false, iis7WorkerRequest);
}
}
/*
* Clear response buffer and headers. Internal. Used to be 'Clear'.
*/
internal void ClearAll() {
if (!_headersWritten)
ClearHeaders();
Clear();
}
/*
* Flush response currently buffered
*/
/// <devdoc>
/// <para>Sends all currently buffered output to the client.</para>
/// </devdoc>
public void Flush() {
if (_completed)
throw new HttpException(SR.GetString(SR.Cannot_flush_completed_response));
Flush(false);
}
// Registers a callback that the ASP.NET runtime will invoke immediately before
// response headers are sent for this request. This differs from the IHttpModule-
// level pipeline event in that this is a per-request subscription rather than
// a per-application subscription. The intent is that the callback may modify
// the response status code or may set a response cookie or header. Other usage
// notes and caveats:
//
// - This API is available only in the IIS integrated mode pipeline and only
// if response headers haven't yet been sent for this request.
// - The ASP.NET runtime does not guarantee anything about the thread that the
// callback is invoked on. For example, the callback may be invoked synchronously
// on a background thread if a background flush is being performed.
// HttpContext.Current is not guaranteed to be available on such a thread.
// - The callback must not call any API that manipulates the response entity body
// or that results in a flush. For example, the callback must not call
// Response.Redirect, as that method may manipulate the response entity body.
// - The callback must contain only short-running synchronous code. Trying to kick
// off an asynchronous operation or wait on such an operation could result in
// a deadlock.
// - The callback must not throw, otherwise behavior is undefined.
[SuppressMessage("Microsoft.Design", "CA1030:UseEventsWhereAppropriate", Justification = @"The normal event pattern doesn't work between HttpResponse and HttpResponseBase since the signatures differ.")]
public ISubscriptionToken AddOnSendingHeaders(Action<HttpContext> callback) {
if (callback == null) {
throw new ArgumentNullException("callback");
}
if (!(_wr is IIS7WorkerRequest)) {
throw new PlatformNotSupportedException(SR.GetString(SR.Requires_Iis_Integrated_Mode));
}
if (HeadersWritten) {
throw new HttpException(SR.GetString(SR.Cannot_call_method_after_headers_sent_generic));
}
return _onSendingHeadersSubscriptionQueue.Enqueue(callback);
}
/*
* Append string to the log record
*
* @param param string to append to the log record
*/
/// <devdoc>
/// <para>Adds custom log information to the IIS log file.</para>
/// </devdoc>
[AspNetHostingPermission(SecurityAction.Demand, Level=AspNetHostingPermissionLevel.Medium)]
public void AppendToLog(String param) {
// only makes sense for IIS
if (_wr is System.Web.Hosting.ISAPIWorkerRequest)
((System.Web.Hosting.ISAPIWorkerRequest)_wr).AppendLogParameter(param);
else if (_wr is System.Web.Hosting.IIS7WorkerRequest)
_context.Request.AppendToLogQueryString(param);
}
/// <devdoc>
/// <para>Redirects a client to a new URL.</para>
/// </devdoc>
public void Redirect(String url) {
Redirect(url, true, false);
}
/// <devdoc>
/// <para>Redirects a client to a new URL.</para>
/// </devdoc>
public void Redirect(String url, bool endResponse) {
Redirect(url, endResponse, false);
}
public void RedirectToRoute(object routeValues) {
RedirectToRoute(new RouteValueDictionary(routeValues));
}
public void RedirectToRoute(string routeName) {
RedirectToRoute(routeName, (RouteValueDictionary)null, false);
}
public void RedirectToRoute(RouteValueDictionary routeValues) {
RedirectToRoute(null /* routeName */, routeValues, false);
}
public void RedirectToRoute(string routeName, object routeValues) {
RedirectToRoute(routeName, new RouteValueDictionary(routeValues), false);
}
public void RedirectToRoute(string routeName, RouteValueDictionary routeValues) {
RedirectToRoute(routeName, routeValues, false);
}
private void RedirectToRoute(string routeName, RouteValueDictionary routeValues, bool permanent) {
string destinationUrl = null;
VirtualPathData data = RouteTable.Routes.GetVirtualPath(Request.RequestContext, routeName, routeValues);
if (data != null) {
destinationUrl = data.VirtualPath;
}
if (String.IsNullOrEmpty(destinationUrl)) {
throw new InvalidOperationException(SR.GetString(SR.No_Route_Found_For_Redirect));
}
Redirect(destinationUrl, false /* endResponse */, permanent);
}
public void RedirectToRoutePermanent(object routeValues) {
RedirectToRoutePermanent(new RouteValueDictionary(routeValues));
}
public void RedirectToRoutePermanent(string routeName) {
RedirectToRoute(routeName, (RouteValueDictionary)null, true);
}
public void RedirectToRoutePermanent(RouteValueDictionary routeValues) {
RedirectToRoute(null /* routeName */, routeValues, true);
}
public void RedirectToRoutePermanent(string routeName, object routeValues) {
RedirectToRoute(routeName, new RouteValueDictionary(routeValues), true);
}
public void RedirectToRoutePermanent(string routeName, RouteValueDictionary routeValues) {
RedirectToRoute(routeName, routeValues, true);
}
/// <devdoc>
/// <para>Redirects a client to a new URL with a 301.</para>
/// </devdoc>
[SuppressMessage("Microsoft.Design", "CA1054:UriParametersShouldNotBeStrings",
Justification="Warning was suppressed for consistency with existing similar Redirect API")]
public void RedirectPermanent(String url) {
Redirect(url, true, true);
}
/// <devdoc>
/// <para>Redirects a client to a new URL with a 301.</para>
/// </devdoc>
[SuppressMessage("Microsoft.Design", "CA1054:UriParametersShouldNotBeStrings",
Justification = "Warning was suppressed for consistency with existing similar Redirect API")]
public void RedirectPermanent(String url, bool endResponse) {
Redirect(url, endResponse, true);
}
internal void Redirect(String url, bool endResponse, bool permanent) {
#if DBG
string originalUrl = url;
#endif
if (url == null)
throw new ArgumentNullException("url");
if (url.IndexOf('\n') >= 0)
throw new ArgumentException(SR.GetString(SR.Cannot_redirect_to_newline));
if (_headersWritten)
throw new HttpException(SR.GetString(SR.Cannot_redirect_after_headers_sent));
Page page = _context.Handler as Page;
if ((page != null) && page.IsCallback) {
throw new ApplicationException(SR.GetString(SR.Redirect_not_allowed_in_callback));
}
url = ApplyRedirectQueryStringIfRequired(url);
url = ApplyAppPathModifier(url);
url = ConvertToFullyQualifiedRedirectUrlIfRequired(url);
url = UrlEncodeRedirect(url);
Clear();
// If it's a Page and SmartNavigation is on, return a short script
// to perform the redirect instead of returning a 302 (bugs ASURT 82331/86782)
#pragma warning disable 0618 // To avoid SmartNavigation deprecation warning
if (page != null && page.IsPostBack && page.SmartNavigation && (Request["__smartNavPostBack"] == "true")) {
#pragma warning restore 0618
Write("<BODY><ASP_SMARTNAV_RDIR url=\"");
Write(HttpUtility.HtmlEncode(url));
Write("\"></ASP_SMARTNAV_RDIR>");
Write("</BODY>");
}
else {
// VSO bug 360276
if(HttpRuntime.UseIntegratedPipeline) {
this.ContentType = "text/html";
}
this.StatusCode = permanent ? 301 : 302;
RedirectLocation = url;
// DevDivBugs 158137: 302 Redirect vulnerable to XSS
// A ---- of protocol identifiers. We don't want to UrlEncode
// URLs matching these schemes in order to not break the
// physical Object Moved to link.
if (UriUtil.IsSafeScheme(url)) {
url = HttpUtility.HtmlAttributeEncode(url);
}
else {
url = HttpUtility.HtmlAttributeEncode(HttpUtility.UrlEncode(url));
}
Write("<html><head><title>Object moved</title></head><body>\r\n");
Write("<h2>Object moved to <a href=\"" + url + "\">here</a>.</h2>\r\n");
Write("</body></html>\r\n");
}
_isRequestBeingRedirected = true;
#if DBG
Debug.Trace("ClientUrl", "*** Redirect (" + originalUrl + ") --> " + RedirectLocation + " ***");
#endif
var redirectingHandler = Redirecting;
if (redirectingHandler != null) {
redirectingHandler(this, EventArgs.Empty);
}
if (endResponse)
End();
}
internal string ApplyRedirectQueryStringIfRequired(string url) {
if (Request == null || (string)Request.Browser["requiresPostRedirectionHandling"] != "true")
return url;
Page page = _context.Handler as Page;
if (page != null && !page.IsPostBack)
return url;
//do not add __redir=1 if it already exists
int i = url.IndexOf(RedirectQueryStringAssignment, StringComparison.Ordinal);
if(i == -1) {
i = url.IndexOf('?');
if (i >= 0) {
url = url.Insert(i + 1, _redirectQueryStringInline);
}
else {
url = String.Concat(url, _redirectQueryString);
}
}
return url;
}
//
// Redirect to error page appending ?aspxerrorpath if no query string in the url.
// Fails to redirect if request is already for error page.
// Suppresses all errors.
// See comments on RedirectToErrorPageStatus type for meaning of return values.
//
internal RedirectToErrorPageStatus RedirectToErrorPage(String url, CustomErrorsRedirectMode redirectMode) {
const String qsErrorMark = "aspxerrorpath";
try {
if (String.IsNullOrEmpty(url))
return RedirectToErrorPageStatus.NotAttempted; // nowhere to redirect
if (_headersWritten)
return RedirectToErrorPageStatus.NotAttempted;
if (Request.QueryString[qsErrorMark] != null)
return RedirectToErrorPageStatus.Failed; // already in error redirect
if (redirectMode == CustomErrorsRedirectMode.ResponseRewrite) {
Context.Server.Execute(url);
}
else {
// append query string
if (url.IndexOf('?') < 0)
url = url + "?" + qsErrorMark + "=" + HttpEncoderUtility.UrlEncodeSpaces(Request.Path);
// redirect without response.end
Redirect(url, false /*endResponse*/);
}
}
catch {
return RedirectToErrorPageStatus.Failed;
}
return RedirectToErrorPageStatus.Success;
}
// Represents the result of calling RedirectToErrorPage
internal enum RedirectToErrorPageStatus {
NotAttempted, // Redirect or rewrite was not attempted, possibly because no redirect URL was specified
Success, // Redirect or rewrite was attempted and succeeded
Failed // Redirect or rewrite was attempted and failed, possibly due to the error page throwing
}
// Implementation of the DefaultHttpHandler for IIS6+
internal bool CanExecuteUrlForEntireResponse {
get {
// if anything is sent, too late
if (_headersWritten) {
return false;
}
// must have the right kind of worker request
if (_wr == null || !_wr.SupportsExecuteUrl) {
return false;
}
// must not be capturing output to custom writer
if (!UsingHttpWriter) {
return false;
}
// there is some cached output not yet sent
if (_httpWriter.GetBufferedLength() != 0) {
return false;
}
// can't use execute url with filter installed
if (_httpWriter.FilterInstalled) {
return false;
}
if (_cachePolicy != null && _cachePolicy.IsModified()) {
return false;
}
return true;
}
}
[SuppressMessage("Microsoft.Security", "CA2122:DoNotIndirectlyExposeMethodsWithLinkDemands", Justification = "This is a safe critical method.")]
internal IAsyncResult BeginExecuteUrlForEntireResponse(
String pathOverride, NameValueCollection requestHeaders,
AsyncCallback cb, Object state) {
Debug.Assert(CanExecuteUrlForEntireResponse);
// prepare user information
String userName, userAuthType;
if (_context != null && _context.User != null) {
userName = _context.User.Identity.Name;
userAuthType = _context.User.Identity.AuthenticationType;
}
else {
userName = String.Empty;
userAuthType = String.Empty;
}
// get the path
String path = Request.RewrittenUrl; // null is ok
if (pathOverride != null) {
path = pathOverride;
}
// get the headers
String headers = null;
if (requestHeaders != null) {
int numHeaders = requestHeaders.Count;
if (numHeaders > 0) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < numHeaders; i++) {
sb.Append(requestHeaders.GetKey(i));
sb.Append(": ");
sb.Append(requestHeaders.Get(i));
sb.Append("\r\n");
}
headers = sb.ToString();
}
}
byte[] entity = null;
if (_context != null && _context.Request != null) {
entity = _context.Request.EntityBody;
}
Debug.Trace("ExecuteUrl", "HttpResponse.BeginExecuteUrlForEntireResponse:" +
" path=" + path + " headers=" + headers +
" userName=" + userName + " authType=" + userAuthType);
// call worker request to start async execute url for this request
IAsyncResult ar = _wr.BeginExecuteUrl(
path,
null, // this method
headers,
true, // let execute url send headers
true, // add user info
_wr.GetUserToken(),
userName,
userAuthType,
entity,
cb,
state);
// suppress further sends from ASP.NET
// (only if succeeded starting async operation - not is 'finally' block)
_headersWritten = true;
_ended = true;
return ar;
}
internal void EndExecuteUrlForEntireResponse(IAsyncResult result) {
Debug.Trace("ExecuteUrl", "HttpResponse.EndExecuteUrlForEntireResponse");
_wr.EndExecuteUrl(result);
}
// Methods to write from file
// Writes values to an HTTP output content stream.
public void Write(String s) {
_writer.Write(s);
}
// Writes values to an HTTP output content stream.
public void Write(Object obj) {
_writer.Write(obj);
}
/// <devdoc>
/// <para>Writes values to an HTTP output content stream.</para>
/// </devdoc>
public void Write(char ch) {
_writer.Write(ch);
}
/// <devdoc>
/// <para>Writes values to an HTTP output content stream.</para>
/// </devdoc>
public void Write(char[] buffer, int index, int count) {
_writer.Write(buffer, index, count);
}
/// <devdoc>
/// <para>Writes a substition block to the response.</para>
/// </devdoc>
public void WriteSubstitution(HttpResponseSubstitutionCallback callback) {
// cannot be instance method on a control
if (callback.Target != null && callback.Target is Control) {
throw new ArgumentException(SR.GetString(SR.Invalid_substitution_callback), "callback");
}
if (UsingHttpWriter) {
// HttpWriter can take substitution blocks
_httpWriter.WriteSubstBlock(callback, _wr as IIS7WorkerRequest);
}
else {
// text writer -- write as string
_writer.Write(callback(_context));
}
// set the cache policy: reduce cachability from public to server
if (_cachePolicy != null && _cachePolicy.GetCacheability() == HttpCacheability.Public)
_cachePolicy.SetCacheability(HttpCacheability.Server);
}
/*
* Helper method to write from file stream
*
* Handles only TextWriter case. For real requests
* HttpWorkerRequest can take files
*/
private void WriteStreamAsText(Stream f, long offset, long size) {
if (size < 0)
size = f.Length - offset;
if (size > 0) {
if (offset > 0)
f.Seek(offset, SeekOrigin.Begin);
byte[] fileBytes = new byte[(int)size];
int bytesRead = f.Read(fileBytes, 0, (int)size);
_writer.Write(Encoding.Default.GetChars(fileBytes, 0, bytesRead));
}
}
// support for VirtualPathProvider
internal void WriteVirtualFile(VirtualFile vf) {
Debug.Trace("WriteVirtualFile", vf.Name);
using (Stream s = vf.Open()) {
if (UsingHttpWriter) {
long size = s.Length;
if (size > 0) {
// write as memory block
byte[] fileBytes = new byte[(int)size];
int bytesRead = s.Read(fileBytes, 0, (int) size);
_httpWriter.WriteBytes(fileBytes, 0, bytesRead);
}
}
else {
// Write file contents
WriteStreamAsText(s, 0, -1);
}
}
}
// Helper method to get absolute physical filename from the argument to WriteFile
private String GetNormalizedFilename(String fn) {
// If it's not a physical path, call MapPath on it
if (!UrlPath.IsAbsolutePhysicalPath(fn)) {
if (Request != null)
fn = Request.MapPath(fn); // relative to current request
else
fn = HostingEnvironment.MapPath(fn);
}
return fn;
}
// Write file
/// Writes a named file directly to an HTTP content output stream.
public void WriteFile(String filename) {
if (filename == null) {
throw new ArgumentNullException("filename");
}
WriteFile(filename, false);
}
/*
* Write file
*
* @param filename file to write
* @readIntoMemory flag to read contents into memory immediately
*/
/// <devdoc>
/// <para> Reads a file into a memory block.</para>
/// </devdoc>
public void WriteFile(String filename, bool readIntoMemory) {
if (filename == null) {
throw new ArgumentNullException("filename");
}
filename = GetNormalizedFilename(filename);
FileStream f = null;
try {
f = new FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.Read);
if (UsingHttpWriter) {
long size = f.Length;
if (size > 0) {
if (readIntoMemory) {
// write as memory block
byte[] fileBytes = new byte[(int)size];
int bytesRead = f.Read(fileBytes, 0, (int) size);
_httpWriter.WriteBytes(fileBytes, 0, bytesRead);
}
else {
// write as file block
f.Close(); // close before writing
f = null;
_httpWriter.WriteFile(filename, 0, size);
}
}
}
else {
// Write file contents
WriteStreamAsText(f, 0, -1);
}
}
finally {
if (f != null)
f.Close();
}
}
public void TransmitFile(string filename) {
TransmitFile(filename, 0, -1);
}
public void TransmitFile(string filename, long offset, long length) {
if (filename == null) {
throw new ArgumentNullException("filename");
}
if (offset < 0)
throw new ArgumentException(SR.GetString(SR.Invalid_range), "offset");
if (length < -1)
throw new ArgumentException(SR.GetString(SR.Invalid_range), "length");
filename = GetNormalizedFilename(filename);
long size;
using (FileStream f = new FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.Read)) {
size = f.Length;
// length of -1 means send rest of file
if (length == -1) {
length = size - offset;
}
if (size < offset) {
throw new ArgumentException(SR.GetString(SR.Invalid_range), "offset");
}
else if ((size - offset) < length) {
throw new ArgumentException(SR.GetString(SR.Invalid_range), "length");
}
if (!UsingHttpWriter) {
WriteStreamAsText(f, offset, length);
return;
}
}
if (length > 0) {
bool supportsLongTransmitFile = (_wr != null && _wr.SupportsLongTransmitFile);
_httpWriter.TransmitFile(filename, offset, length,
_context.IsClientImpersonationConfigured || HttpRuntime.IsOnUNCShareInternal, supportsLongTransmitFile);
}
}
private void ValidateFileRange(String filename, long offset, long length) {
FileStream f = null;
try {
f = new FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.Read);
long fileSize = f.Length;
if (length == -1)
length = fileSize - offset;
if (offset < 0 || length > fileSize - offset)
throw new HttpException(SR.GetString(SR.Invalid_range));
}
finally {
if (f != null)
f.Close();
}
}
/*
* Write file
*
* @param filename file to write
* @param offset file offset to start writing
* @param size number of bytes to write
*/
/// <devdoc>
/// <para>Writes a file directly to an HTTP content output stream.</para>
/// </devdoc>
public void WriteFile(String filename, long offset, long size) {
if (filename == null) {
throw new ArgumentNullException("filename");
}
if (size == 0)
return;
filename = GetNormalizedFilename(filename);
ValidateFileRange(filename, offset, size);
if (UsingHttpWriter) {
// HttpWriter can take files -- don't open here (but Demand permission)
InternalSecurityPermissions.FileReadAccess(filename).Demand();
_httpWriter.WriteFile(filename, offset, size);
}
else {
FileStream f = null;
try {
f = new FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.Read);
WriteStreamAsText(f, offset, size);
}
finally {
if (f != null)
f.Close();
}
}
}
/*
* Write file
*
* @param handle file to write
* @param offset file offset to start writing
* @param size number of bytes to write
*/
/// <devdoc>
/// <para>Writes a file directly to an HTTP content output stream.</para>
/// </devdoc>
[SecurityPermission(SecurityAction.Demand, UnmanagedCode=true)]
public void WriteFile(IntPtr fileHandle, long offset, long size) {
if (size <= 0)
return;
FileStream f = null;
try {
f = new FileStream(new Microsoft.Win32.SafeHandles.SafeFileHandle(fileHandle,false), FileAccess.Read);
if (UsingHttpWriter) {
long fileSize = f.Length;
if (size == -1)
size = fileSize - offset;
if (offset < 0 || size > fileSize - offset)
throw new HttpException(SR.GetString(SR.Invalid_range));
if (offset > 0)
f.Seek(offset, SeekOrigin.Begin);
// write as memory block
byte[] fileBytes = new byte[(int)size];
int bytesRead = f.Read(fileBytes, 0, (int)size);
_httpWriter.WriteBytes(fileBytes, 0, bytesRead);
}
else {
WriteStreamAsText(f, offset, size);
}
}
finally {
if (f != null)
f.Close();
}
}
/// <devdoc>
/// <para>Allows HTTP/2 Server Push</para>
/// </devdoc>
public void PushPromise(string path) {
//
PushPromise(path, method: "GET", headers: null);
}
/// <devdoc>
/// <para>Allows HTTP/2 Server Push</para>
/// </devdoc>
public void PushPromise(string path, string method, NameValueCollection headers) {
// PushPromise is non-deterministic and application shouldn't have logic that depends on it.
// It's only purpose is performance advantage in some cases.
// There are many conditions (protocol and implementation) that may cause to
// ignore the push requests completely.
// The expectation is based on fire-and-forget
if (path == null) {
throw new ArgumentNullException("path");
}
if (method == null) {
throw new ArgumentNullException("method");
}
// Extract an optional query string
string queryString = string.Empty;
int i = path.IndexOf('?');
if (i >= 0) {
if (i < path.Length - 1) {
queryString = path.Substring(i + 1);
}
// Remove the query string portion from the path
path = path.Substring(0, i);
}
// Only virtual path is allowed:
// "/path" - origin relative
// "~/path" - app relative
// "path" - request relative
// "../path" - reduced
if (string.IsNullOrEmpty(path) || !UrlPath.IsValidVirtualPathWithoutProtocol(path)) {
throw new ArgumentException(SR.GetString(SR.Invalid_path_for_push_promise, path));
}
VirtualPath virtualPath = Request.FilePathObject.Combine(VirtualPath.Create(path));
try {
if (!HttpRuntime.UseIntegratedPipeline) {
throw new PlatformNotSupportedException(SR.GetString(SR.Requires_Iis_Integrated_Mode));
}
// Do push promise
IIS7WorkerRequest wr = (IIS7WorkerRequest) _wr;
wr.PushPromise(virtualPath.VirtualPathString, queryString, method, headers);
}
catch (PlatformNotSupportedException e) {
// Ignore errors if push promise is not supported
if (Context.TraceIsEnabled) {
Context.Trace.Write("aspx", "Push promise is not supported", e);
}
}
}
//
// Deprecated ASP compatibility methods and properties
//
/// <devdoc>
/// <para>
/// Same as StatusDescription. Provided only for ASP compatibility.
/// </para>
/// </devdoc>
public string Status {
get {
return this.StatusCode.ToString(NumberFormatInfo.InvariantInfo) + " " + this.StatusDescription;
}
set {
int code = 200;
String descr = "OK";
try {
int i = value.IndexOf(' ');
code = Int32.Parse(value.Substring(0, i), CultureInfo.InvariantCulture);
descr = value.Substring(i+1);
}
catch {
throw new HttpException(SR.GetString(SR.Invalid_status_string));
}
this.StatusCode = code;
this.StatusDescription = descr;
}
}
/// <devdoc>
/// <para>
/// Same as BufferOutput. Provided only for ASP compatibility.
/// </para>
/// </devdoc>
public bool Buffer {
get { return this.BufferOutput;}
set { this.BufferOutput = value;}
}
/// <devdoc>
/// <para>Same as Appendheader. Provided only for ASP compatibility.</para>
/// </devdoc>
public void AddHeader(String name, String value) {
AppendHeader(name, value);
}
/*
* Cancelles handler processing of the current request
* throws special [non-]exception uncatchable by the user code
* to tell application to stop module execution.
*/
/// <devdoc>
/// <para>Sends all currently buffered output to the client then closes the
/// socket connection.</para>
/// </devdoc>
public void End() {
if (_context.IsInCancellablePeriod) {
AbortCurrentThread();
}
else {
// when cannot abort execution, flush and supress further output
_endRequiresObservation = true;
if (!_flushing) { // ignore Reponse.End while flushing (in OnPreSendHeaders)
Flush();
_ended = true;
if (_context.ApplicationInstance != null) {
_context.ApplicationInstance.CompleteRequest();
}
}
}
}
// Aborts the current thread if Response.End was called and not yet observed.
internal void ObserveResponseEndCalled() {
if (_endRequiresObservation) {
_endRequiresObservation = false;
AbortCurrentThread();
}
}
[SuppressMessage("Microsoft.Security", "CA2106:SecureAsserts", Justification = "Known issue, but required for proper operation of ASP.NET.")]
[SecurityPermission(SecurityAction.Assert, ControlThread = true)]
private static void AbortCurrentThread() {
Thread.CurrentThread.Abort(new HttpApplication.CancelModuleException(false));
}
/*
* ASP compatible caching properties
*/
/// <devdoc>
/// <para>
/// Gets or sets the time, in minutes, until cached
/// information will be removed from the cache. Provided for ASP compatiblility. Use
/// the <see cref='System.Web.HttpResponse.Cache'/>
/// Property instead.
/// </para>
/// </devdoc>
public int Expires {
get {
return _expiresInMinutes;
}
set {
if (!_expiresInMinutesSet || value < _expiresInMinutes) {
_expiresInMinutes = value;
Cache.SetExpires(_context.Timestamp + new TimeSpan(0, _expiresInMinutes, 0));
}
}
}
/// <devdoc>
/// <para>
/// Gets or sets the absolute time that cached information
/// will be removed from the cache. Provided for ASP compatiblility. Use the <see cref='System.Web.HttpResponse.Cache'/>
/// property instead.
/// </para>
/// </devdoc>
public DateTime ExpiresAbsolute {
get {
return _expiresAbsolute;
}
set {
if (!_expiresAbsoluteSet || value < _expiresAbsolute) {
_expiresAbsolute = value;
Cache.SetExpires(_expiresAbsolute);
}
}
}
/// <devdoc>
/// <para>
/// Provided for ASP compatiblility. Use the <see cref='System.Web.HttpResponse.Cache'/>
/// property instead.
/// </para>
/// </devdoc>
public string CacheControl {
get {
if (_cacheControl == null) {
// the default
return "private";
}
return _cacheControl;
}
set {
if (String.IsNullOrEmpty(value)) {
_cacheControl = null;
Cache.SetCacheability(HttpCacheability.NoCache);
}
else if (StringUtil.EqualsIgnoreCase(value, "private")) {
_cacheControl = value;
Cache.SetCacheability(HttpCacheability.Private);
}
else if (StringUtil.EqualsIgnoreCase(value, "public")) {
_cacheControl = value;
Cache.SetCacheability(HttpCacheability.Public);
}
else if (StringUtil.EqualsIgnoreCase(value, "no-cache")) {
_cacheControl = value;
Cache.SetCacheability(HttpCacheability.NoCache);
}
else {
throw new ArgumentException(SR.GetString(SR.Invalid_value_for_CacheControl, value));
}
}
}
internal void SetAppPathModifier(string appPathModifier) {
if (appPathModifier != null && (
appPathModifier.Length == 0 ||
appPathModifier[0] == '/' ||
appPathModifier[appPathModifier.Length - 1] == '/')) {
throw new ArgumentException(SR.GetString(SR.InvalidArgumentValue, "appPathModifier"));
}
_appPathModifier = appPathModifier;
Debug.Trace("ClientUrl", "*** SetAppPathModifier (" + appPathModifier + ") ***");
}
public string ApplyAppPathModifier(string virtualPath) {
#if DBG
string originalUrl = virtualPath;
#endif
object ch = _context.CookielessHelper; // This ensures that the cookieless-helper is initialized and applies the AppPathModifier
if (virtualPath == null)
return null;
if (UrlPath.IsRelativeUrl(virtualPath)) {
// DevDiv 173208: RewritePath returns an HTTP 500 error code when requested with certain user agents
// We should use ClientBaseDir instead of FilePathObject.
virtualPath = UrlPath.Combine(Request.ClientBaseDir.VirtualPathString, virtualPath);
}
else {
// ignore paths with http://server/... or //
if (!UrlPath.IsRooted(virtualPath) || virtualPath.StartsWith("//", StringComparison.Ordinal)) {
return virtualPath;
}
virtualPath = UrlPath.Reduce(virtualPath);
}
if (_appPathModifier == null || virtualPath.IndexOf(_appPathModifier, StringComparison.Ordinal) >= 0) {
#if DBG
Debug.Trace("ClientUrl", "*** ApplyAppPathModifier (" + originalUrl + ") --> " + virtualPath + " ***");
#endif
return virtualPath;
}
string appPath = HttpRuntime.AppDomainAppVirtualPathString;
int compareLength = appPath.Length;
bool isVirtualPathShort = (virtualPath.Length == appPath.Length - 1);
if (isVirtualPathShort) {
compareLength--;
}
// String.Compare will throw exception if there aren't compareLength characters
if (virtualPath.Length < compareLength) {
return virtualPath;
}
if (!StringUtil.EqualsIgnoreCase(virtualPath, 0, appPath, 0, compareLength)) {
return virtualPath;
}
if (isVirtualPathShort) {
virtualPath += "/";
}
Debug.Assert(virtualPath.Length >= appPath.Length);
if (virtualPath.Length == appPath.Length) {
virtualPath = virtualPath.Substring(0, appPath.Length) + _appPathModifier + "/";
}
else {
virtualPath =
virtualPath.Substring(0, appPath.Length) +
_appPathModifier +
"/" +
virtualPath.Substring(appPath.Length);
}
#if DBG
Debug.Trace("ClientUrl", "*** ApplyAppPathModifier (" + originalUrl + ") --> " + virtualPath + " ***");
#endif
return virtualPath;
}
internal String RemoveAppPathModifier(string virtualPath) {
if (String.IsNullOrEmpty(_appPathModifier))
return virtualPath;
int pos = virtualPath.IndexOf(_appPathModifier, StringComparison.Ordinal);
if (pos <= 0 || virtualPath[pos-1] != '/')
return virtualPath;
return virtualPath.Substring(0, pos-1) + virtualPath.Substring(pos + _appPathModifier.Length);
}
internal bool UsePathModifier {
get {
return !String.IsNullOrEmpty(_appPathModifier);
}
}
private String ConvertToFullyQualifiedRedirectUrlIfRequired(String url) {
HttpRuntimeSection runtimeConfig = _context.IsRuntimeErrorReported ?
RuntimeConfig.GetLKGConfig(_context).HttpRuntime : RuntimeConfig.GetConfig(_context).HttpRuntime;
if ( runtimeConfig.UseFullyQualifiedRedirectUrl ||
(Request != null && (string)Request.Browser["requiresFullyQualifiedRedirectUrl"] == "true")) {
return (new Uri(Request.Url, url)).AbsoluteUri ;
}
else {
return url;
}
}
private String UrlEncodeIDNSafe(String url) {
// Bug 86594: Should not encode the domain part of the url. For example,
// http://Übersite/Überpage.aspx should only encode the 2nd Ü.
// To accomplish this we must separate the scheme+host+port portion of the url from the path portion,
// encode the path portion, then reconstruct the url.
Debug.Assert(!url.Contains("?"), "Querystring should have been stripped off.");
string schemeAndAuthority;
string path;
string queryAndFragment;
bool isValidUrl = UriUtil.TrySplitUriForPathEncode(url, out schemeAndAuthority, out path, out queryAndFragment, checkScheme: true);
if (isValidUrl) {
// only encode the path portion
return schemeAndAuthority + HttpEncoderUtility.UrlEncodeSpaces(HttpUtility.UrlEncodeNonAscii(path, Encoding.UTF8)) + queryAndFragment;
}
else {
// encode the entire URL
return HttpEncoderUtility.UrlEncodeSpaces(HttpUtility.UrlEncodeNonAscii(url, Encoding.UTF8));
}
}
private String UrlEncodeRedirect(String url) {
// convert all non-ASCII chars before ? to %XX using UTF-8 and
// after ? using Response.ContentEncoding
int iqs = url.IndexOf('?');
if (iqs >= 0) {
Encoding qsEncoding = (Request != null) ? Request.ContentEncoding : ContentEncoding;
url = UrlEncodeIDNSafe(url.Substring(0, iqs)) + HttpUtility.UrlEncodeNonAscii(url.Substring(iqs), qsEncoding);
}
else {
url = UrlEncodeIDNSafe(url);
}
return url;
}
internal void UpdateNativeResponse(bool sendHeaders)
{
IIS7WorkerRequest iis7WorkerRequest = _wr as IIS7WorkerRequest;
if (null == iis7WorkerRequest) {
return;
}
// WOS 1841024 - Don't set _suppressContent to true for HEAD requests. IIS needs the content
// in order to correctly set the Content-Length header.
// WOS 1634512 - need to clear buffers if _ended == true
// WOS 1850019 - Breaking Change: ASP.NET v2.0: Content-Length is not correct for pages that call HttpResponse.SuppressContent
if ((_suppressContent && Request != null && Request.HttpVerb != HttpVerb.HEAD) || _ended)
Clear();
bool needPush = false;
// NOTE: This also sets the response encoding on the HttpWriter
long bufferedLength = _httpWriter.GetBufferedLength();
//
// Set headers and status
//
if (!_headersWritten)
{
//
// Set status
//
// VSWhidbey 270635: We need to reset the status code for mobile devices.
if (UseAdaptiveError) {
// VSWhidbey 288054: We should change the status code for cases
// that cannot be handled by mobile devices
// 4xx for Client Error and 5xx for Server Error in HTTP spec
int statusCode = StatusCode;
if (statusCode >= 400 && statusCode < 600) {
this.StatusCode = 200;
}
}
// DevDiv #782830: Provide a hook where the application can change the response status code
// or response headers.
if (sendHeaders && !_onSendingHeadersSubscriptionQueue.IsEmpty) {
_onSendingHeadersSubscriptionQueue.FireAndComplete(cb => cb(Context));
}
if (_statusSet) {
_wr.SendStatus(this.StatusCode, this.SubStatusCode, this.StatusDescription);
_statusSet = false;
}
//
// Set headers
//
if (!_suppressHeaders && !_clientDisconnected)
{
if (sendHeaders) {
EnsureSessionStateIfNecessary();
}
// If redirect location set, write it through to IIS as a header
if (_redirectLocation != null && _redirectLocationSet) {
HttpHeaderCollection headers = Headers as HttpHeaderCollection;
headers.Set("Location", _redirectLocation);
_redirectLocationSet = false;
}
// Check if there is buffered response
bool responseBuffered = bufferedLength > 0 || iis7WorkerRequest.IsResponseBuffered();
//
// Generate Content-Type
//
if (_contentType != null // Valid Content-Type
&& (_contentTypeSetByManagedCaller // Explicitly set by managed caller
|| (_contentTypeSetByManagedHandler && responseBuffered))) { // Implicitly set by managed handler and response is non-empty
HttpHeaderCollection headers = Headers as HttpHeaderCollection;
String contentType = AppendCharSetToContentType(_contentType);
headers.Set("Content-Type", contentType);
}
//
// If cookies have been added/changed, set the corresponding headers
//
GenerateResponseHeadersForCookies();
// Not calling WriteHeaders headers in Integrated mode.
// Instead, most headers are generated when the handler runs,
// or on demand as necessary.
// The only exception are the cache policy headers.
if (sendHeaders) {
SuppressCachingCookiesIfNecessary();
if (_cachePolicy != null) {
if (_cachePolicy.IsModified()) {
ArrayList cacheHeaders = new ArrayList();
_cachePolicy.GetHeaders(cacheHeaders, this);
HttpHeaderCollection headers = Headers as HttpHeaderCollection;
foreach (HttpResponseHeader header in cacheHeaders) {
// set and override the header
headers.Set(header.Name, header.Value);
}
}
}
needPush = true;
}
}
}
if (_flushing && !_filteringCompleted) {
_httpWriter.FilterIntegrated(false, iis7WorkerRequest);
bufferedLength = _httpWriter.GetBufferedLength();
}
if (!_clientDisconnected && (bufferedLength > 0 || needPush)) {
if (bufferedLength == 0 ) {
if (_httpWriter.IgnoringFurtherWrites) {
return;
}
}
// push HttpWriter buffers to worker request
_httpWriter.Send(_wr);
// push buffers through into native
iis7WorkerRequest.PushResponseToNative();
// dispose them (since they're copied or
// owned by native request)
_httpWriter.DisposeIntegratedBuffers();
}
}
private void ClearNativeResponse(bool clearEntity, bool clearHeaders, IIS7WorkerRequest wr) {
wr.ClearResponse(clearEntity, clearHeaders);
if (clearEntity) {
_httpWriter.ClearSubstitutionBlocks();
}
}
private void SuppressCachingCookiesIfNecessary() {
// MSRC 11855 (DevDiv 297240 / 362405)
// We should suppress caching cookies if non-shareable cookies are
// present in the response. Since these cookies can cary sensitive information,
// we should set Cache-Control: no-cache=set-cookie if there is such cookie
// This prevents all well-behaved caches (both intermediary proxies and any local caches
// on the client) from storing this sensitive information.
//
// Additionally, we should not set this header during an SSL request, as certain versions
// of IE don't handle it properly and simply refuse to render the page. More info:
// http://blogs.msdn.com/b/ieinternals/archive/2009/10/02/internet-explorer-cannot-download-over-https-when-no-cache.aspx
//
// Finally, we don't need to set 'no-cache' if the response is not publicly cacheable,
// as ASP.NET won't cache the response (due to the cookies) and proxies won't cache
// the response (due to Cache-Control: private).
// If _cachePolicy isn't set, then Cache.GetCacheability() will contruct a default one (which causes Cache-Control: private)
if (!Request.IsSecureConnection && ContainsNonShareableCookies() && Cache.GetCacheability() == HttpCacheability.Public) {
Cache.SetCacheability(HttpCacheability.NoCache, "Set-Cookie");
}
// if there are any cookies, do not kernel cache the response
if (_cachePolicy != null && _cookies != null && _cookies.Count != 0) {
_cachePolicy.SetHasSetCookieHeader();
// In integrated mode, the cookies will eventually be sent to IIS via IIS7WorkerRequest.SetUnknownResponseHeader,
// where we will disable both HTTP.SYS kernel cache and IIS user mode cache (DevDiv 113142 & 255268). In classic
// mode, the cookies will be sent to IIS via ISAPIWorkerRequest.SendUnknownResponseHeader and
// ISAPIWorkerRequest.SendKnownResponseHeader (DevDiv 113142), where we also disables the kernel cache. So the
// call of DisableKernelCache below is not really needed.
DisableKernelCache();
}
}
private void EnsureSessionStateIfNecessary() {
// Ensure the session state is in complete state before sending the response headers
// Due to optimization and delay initialization sometimes we create and store the session state id in ReleaseSessionState.
// But it's too late in case of Flush. Session state id must be written (if used) before sending the headers.
if (AppSettings.EnsureSessionStateLockedOnFlush) {
_context.EnsureSessionStateIfNecessary();
}
}
}
internal enum CacheDependencyType {
Files,
CacheItems,
VirtualPaths
}
struct ResponseDependencyList {
private ArrayList _dependencies;
private string[] _dependencyArray;
private DateTime _oldestDependency;
private string _requestVirtualPath;
internal void AddDependency(string item, string argname) {
if (item == null) {
throw new ArgumentNullException(argname);
}
_dependencyArray = null;
if (_dependencies == null) {
_dependencies = new ArrayList(1);
}
DateTime utcNow = DateTime.UtcNow;
_dependencies.Add(new ResponseDependencyInfo(
new string[] {item}, utcNow));
// _oldestDependency is initialized to MinValue and indicates that it must always be set
if (_oldestDependency == DateTime.MinValue || utcNow < _oldestDependency)
_oldestDependency = utcNow;
}
internal void AddDependencies(ArrayList items, string argname) {
if (items == null) {
throw new ArgumentNullException(argname);
}
string[] a = (string[]) items.ToArray(typeof(string));
AddDependencies(a, argname, false);
}
internal void AddDependencies(string[] items, string argname) {
AddDependencies(items, argname, true);
}
internal void AddDependencies(string[] items, string argname, bool cloneArray) {
AddDependencies(items, argname, cloneArray, DateTime.UtcNow);
}
internal void AddDependencies(string[] items, string argname, bool cloneArray, string requestVirtualPath) {
if (requestVirtualPath == null)
throw new ArgumentNullException("requestVirtualPath");
_requestVirtualPath = requestVirtualPath;
AddDependencies(items, argname, cloneArray, DateTime.UtcNow);
}
internal void AddDependencies(string[] items, string argname, bool cloneArray, DateTime utcDepTime) {
if (items == null) {
throw new ArgumentNullException(argname);
}
string [] itemsLocal;
if (cloneArray) {
itemsLocal = (string[]) items.Clone();
}
else {
itemsLocal = items;
}
foreach (string item in itemsLocal) {
if (String.IsNullOrEmpty(item)) {
throw new ArgumentNullException(argname);
}
}
_dependencyArray = null;
if (_dependencies == null) {
_dependencies = new ArrayList(1);
}
_dependencies.Add(new ResponseDependencyInfo(itemsLocal, utcDepTime));
// _oldestDependency is initialized to MinValue and indicates that it must always be set
if (_oldestDependency == DateTime.MinValue || utcDepTime < _oldestDependency)
_oldestDependency = utcDepTime;
}
internal bool HasDependencies() {
if (_dependencyArray == null && _dependencies == null)
return false;
return true;
}
internal string[] GetDependencies() {
if (_dependencyArray == null && _dependencies != null) {
int size = 0;
foreach (ResponseDependencyInfo info in _dependencies) {
size += info.items.Length;
}
_dependencyArray = new string[size];
int index = 0;
foreach (ResponseDependencyInfo info in _dependencies) {
int length = info.items.Length;
Array.Copy(info.items, 0, _dependencyArray, index, length);
index += length;
}
}
return _dependencyArray;
}
// The caller of this method must dispose the cache dependencies
internal CacheDependency CreateCacheDependency(CacheDependencyType dependencyType, CacheDependency dependency) {
if (_dependencies != null) {
if (dependencyType == CacheDependencyType.Files
|| dependencyType == CacheDependencyType.CacheItems) {
foreach (ResponseDependencyInfo info in _dependencies) {
CacheDependency dependencyOld = dependency;
try {
if (dependencyType == CacheDependencyType.Files) {
dependency = new CacheDependency(0, info.items, null, dependencyOld, info.utcDate);
}
else {
// We create a "public" CacheDepdency here, since the keys are for public items.
dependency = new CacheDependency(null, info.items, dependencyOld,
DateTimeUtil.ConvertToLocalTime(info.utcDate));
}
}
finally {
if (dependencyOld != null) {
dependencyOld.Dispose();
}
}
}
}
else {
CacheDependency virtualDependency = null;
VirtualPathProvider vpp = HostingEnvironment.VirtualPathProvider;
if (vpp != null && _requestVirtualPath != null) {
virtualDependency = vpp.GetCacheDependency(_requestVirtualPath, GetDependencies(), _oldestDependency);
}
if (virtualDependency != null) {
AggregateCacheDependency tempDep = new AggregateCacheDependency();
tempDep.Add(virtualDependency);
if (dependency != null) {
tempDep.Add(dependency);
}
dependency = tempDep;
}
}
}
return dependency;
}
}
internal class ResponseDependencyInfo {
internal readonly string[] items;
internal readonly DateTime utcDate;
internal ResponseDependencyInfo(string[] items, DateTime utcDate) {
this.items = items;
this.utcDate = utcDate;
}
}
}
|