0cb742dafb
Rewrite with hard-coded offsets into the PE file format to discern if a binary is PE32 or PE32+, and then to determine if it contains a "CLR Data Directory" entry that looks valid. Tested with PE32 and PE32+ compiled Mono binaries, PE32 and PE32+ native binaries, and a random assortment of garbage files. Former-commit-id: 9e7ac86ec84f653a2f79b87183efd5b0ebda001b
3700 lines
141 KiB
C#
3700 lines
141 KiB
C#
//------------------------------------------------------------------------------
|
||
// <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) {
|
||
if (!cookie.IsInResponseHeader) {
|
||
// 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.IsInResponseHeader = true;
|
||
}
|
||
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.IsInResponseHeader = true;
|
||
cookie.Added = false;
|
||
cookie.Changed = false;
|
||
}
|
||
|
||
_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://<2F>bersite/<2F>berpage.aspx should only encode the 2nd <20>.
|
||
// 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;
|
||
}
|
||
}
|
||
}
|
||
|