e79aa3c0ed
Former-commit-id: a2155e9bd80020e49e72e86c44da02a8ac0e57a4
1830 lines
60 KiB
C#
1830 lines
60 KiB
C#
//------------------------------------------------------------------------------
|
|
// <copyright file="HttpWriter.cs" company="Microsoft">
|
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
|
// </copyright>
|
|
//------------------------------------------------------------------------------
|
|
|
|
/*
|
|
* Response Writer and Stream implementation
|
|
*
|
|
* Copyright (c) 1998 Microsoft Corporation
|
|
*/
|
|
|
|
namespace System.Web {
|
|
using System.Collections;
|
|
using System.Globalization;
|
|
using System.IO;
|
|
using System.Runtime.Serialization.Formatters;
|
|
using System.Security.Permissions;
|
|
using System.Text;
|
|
using System.Threading;
|
|
using System.Web.Util;
|
|
using System.Web.Hosting;
|
|
|
|
using IIS = System.Web.Hosting.UnsafeIISMethods;
|
|
|
|
//
|
|
// HttpWriter buffer recycling support
|
|
//
|
|
|
|
/*
|
|
* Constants for buffering
|
|
*/
|
|
internal static class BufferingParams {
|
|
internal static readonly int INTEGRATED_MODE_BUFFER_SIZE = 16*1024 - 4*IntPtr.Size; // native buffer size for integrated mode
|
|
internal const int OUTPUT_BUFFER_SIZE = 31*1024; // output is a chain of this size buffers
|
|
internal const int MAX_FREE_BYTES_TO_CACHE = 4096; // don't compress when taking snapshot if free bytes < this
|
|
internal const int MAX_FREE_OUTPUT_BUFFERS = 64; // keep this number of unused buffers
|
|
internal const int CHAR_BUFFER_SIZE = 1024; // size of the buffers for chat conversion to bytes
|
|
internal const int MAX_FREE_CHAR_BUFFERS = 64; // keep this number of unused buffers
|
|
internal const int MAX_BYTES_TO_COPY = 128; // copy results of char conversion vs using recycleable buffers
|
|
internal const int MAX_RESOURCE_BYTES_TO_COPY = 4*1024; // resource strings below this size are copied to buffers
|
|
internal const int INT_BUFFER_SIZE = 128; // default size for int[] buffers
|
|
internal const int INTPTR_BUFFER_SIZE = 128; // default size for IntPtr[] buffers
|
|
}
|
|
|
|
/*
|
|
* Interface implemented by elements of the response buffer list
|
|
*/
|
|
internal interface IHttpResponseElement {
|
|
long GetSize();
|
|
byte[] GetBytes(); // required for filtering
|
|
void Send(HttpWorkerRequest wr);
|
|
}
|
|
|
|
/*
|
|
* Base class for recyclable memory buffer elements
|
|
*/
|
|
internal abstract class HttpBaseMemoryResponseBufferElement {
|
|
|
|
protected int _size;
|
|
protected int _free;
|
|
protected bool _recycle;
|
|
|
|
internal int FreeBytes {
|
|
get { return _free;}
|
|
}
|
|
|
|
internal void DisableRecycling() {
|
|
_recycle = false;
|
|
}
|
|
|
|
// abstract methods
|
|
|
|
internal abstract void Recycle();
|
|
|
|
internal abstract HttpResponseBufferElement Clone();
|
|
|
|
internal abstract int Append(byte[] data, int offset, int size);
|
|
|
|
internal abstract int Append(IntPtr data, int offset, int size);
|
|
|
|
internal abstract void AppendEncodedChars(char[] data, int offset, int size, Encoder encoder, bool flushEncoder);
|
|
}
|
|
|
|
/*
|
|
* Memory response buffer
|
|
*/
|
|
internal sealed class HttpResponseBufferElement : HttpBaseMemoryResponseBufferElement, IHttpResponseElement {
|
|
private byte[] _data;
|
|
|
|
/*
|
|
* Constructor that accepts the data buffer and holds on to it
|
|
*/
|
|
internal HttpResponseBufferElement(byte[] data, int size) {
|
|
_data = data;
|
|
_size = size;
|
|
_free = 0;
|
|
_recycle = false;
|
|
}
|
|
|
|
/*
|
|
* Close the buffer copying the data
|
|
* (needed to 'compress' buffers for caching)
|
|
*/
|
|
|
|
internal override HttpResponseBufferElement Clone() {
|
|
int clonedSize = _size - _free;
|
|
byte[] clonedData = new byte[clonedSize];
|
|
Buffer.BlockCopy(_data, 0, clonedData, 0, clonedSize);
|
|
return new HttpResponseBufferElement(clonedData, clonedSize);
|
|
}
|
|
|
|
internal override void Recycle() {
|
|
|
|
}
|
|
|
|
internal override int Append(byte[] data, int offset, int size) {
|
|
if (_free == 0 || size == 0)
|
|
return 0;
|
|
int n = (_free >= size) ? size : _free;
|
|
Buffer.BlockCopy(data, offset, _data, _size-_free, n);
|
|
_free -= n;
|
|
return n;
|
|
}
|
|
|
|
internal override int Append(IntPtr data, int offset, int size) {
|
|
if (_free == 0 || size == 0)
|
|
return 0;
|
|
int n = (_free >= size) ? size : _free;
|
|
Misc.CopyMemory(data, offset, _data, _size-_free, n);
|
|
_free -= n;
|
|
return n;
|
|
}
|
|
|
|
internal override void AppendEncodedChars(char[] data, int offset, int size, Encoder encoder, bool flushEncoder) {
|
|
int byteSize = encoder.GetBytes(data, offset, size, _data, _size-_free, flushEncoder);
|
|
_free -= byteSize;
|
|
}
|
|
|
|
//
|
|
// IHttpResponseElement implementation
|
|
//
|
|
|
|
/*
|
|
* Get number of bytes
|
|
*/
|
|
long IHttpResponseElement.GetSize() {
|
|
return(_size - _free);
|
|
}
|
|
|
|
/*
|
|
* Get bytes (for filtering)
|
|
*/
|
|
byte[] IHttpResponseElement.GetBytes() {
|
|
return _data;
|
|
}
|
|
|
|
/*
|
|
* Write HttpWorkerRequest
|
|
*/
|
|
void IHttpResponseElement.Send(HttpWorkerRequest wr) {
|
|
int n = _size - _free;
|
|
if (n > 0)
|
|
wr.SendResponseFromMemory(_data, n);
|
|
}
|
|
}
|
|
|
|
#if !FEATURE_PAL // FEATURE_PAL does not enable IIS-based hosting features
|
|
/*
|
|
* Unmanaged memory response buffer
|
|
*/
|
|
internal sealed class HttpResponseUnmanagedBufferElement : HttpBaseMemoryResponseBufferElement, IHttpResponseElement {
|
|
private IntPtr _data;
|
|
private static IntPtr s_Pool;
|
|
|
|
static HttpResponseUnmanagedBufferElement() {
|
|
if (HttpRuntime.UseIntegratedPipeline) {
|
|
s_Pool = IIS.MgdGetBufferPool(BufferingParams.INTEGRATED_MODE_BUFFER_SIZE);
|
|
}
|
|
else {
|
|
s_Pool = UnsafeNativeMethods.BufferPoolGetPool(BufferingParams.OUTPUT_BUFFER_SIZE,
|
|
BufferingParams.MAX_FREE_OUTPUT_BUFFERS);
|
|
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Constructor that creates an empty buffer
|
|
*/
|
|
internal HttpResponseUnmanagedBufferElement() {
|
|
if (HttpRuntime.UseIntegratedPipeline) {
|
|
_data = IIS.MgdGetBuffer(s_Pool);
|
|
_size = BufferingParams.INTEGRATED_MODE_BUFFER_SIZE;
|
|
}
|
|
else {
|
|
_data = UnsafeNativeMethods.BufferPoolGetBuffer(s_Pool);
|
|
_size = BufferingParams.OUTPUT_BUFFER_SIZE;
|
|
}
|
|
if (_data == IntPtr.Zero) {
|
|
throw new OutOfMemoryException();
|
|
}
|
|
_free = _size;
|
|
_recycle = true;
|
|
}
|
|
|
|
/*
|
|
* dtor - frees the unmanaged buffer
|
|
*/
|
|
~HttpResponseUnmanagedBufferElement() {
|
|
IntPtr data = Interlocked.Exchange(ref _data, IntPtr.Zero);
|
|
if (data != IntPtr.Zero) {
|
|
if (HttpRuntime.UseIntegratedPipeline) {
|
|
IIS.MgdReturnBuffer(data);
|
|
}
|
|
else {
|
|
UnsafeNativeMethods.BufferPoolReleaseBuffer(data);
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Clone the buffer copying the data int managed buffer
|
|
* (needed to 'compress' buffers for caching)
|
|
*/
|
|
internal override HttpResponseBufferElement Clone() {
|
|
int clonedSize = _size - _free;
|
|
byte[] clonedData = new byte[clonedSize];
|
|
Misc.CopyMemory(_data, 0, clonedData, 0, clonedSize);
|
|
return new HttpResponseBufferElement(clonedData, clonedSize);
|
|
}
|
|
|
|
internal override void Recycle() {
|
|
if (_recycle)
|
|
ForceRecycle();
|
|
}
|
|
|
|
private void ForceRecycle() {
|
|
IntPtr data = Interlocked.Exchange(ref _data, IntPtr.Zero);
|
|
if (data != IntPtr.Zero) {
|
|
_free = 0;
|
|
_recycle = false;
|
|
if (HttpRuntime.UseIntegratedPipeline) {
|
|
IIS.MgdReturnBuffer(data);
|
|
}
|
|
else {
|
|
UnsafeNativeMethods.BufferPoolReleaseBuffer(data);
|
|
}
|
|
System.GC.SuppressFinalize(this);
|
|
}
|
|
}
|
|
|
|
internal override int Append(byte[] data, int offset, int size) {
|
|
if (_free == 0 || size == 0)
|
|
return 0;
|
|
int n = (_free >= size) ? size : _free;
|
|
Misc.CopyMemory(data, offset, _data, _size-_free, n);
|
|
_free -= n;
|
|
return n;
|
|
}
|
|
|
|
internal override int Append(IntPtr data, int offset, int size) {
|
|
if (_free == 0 || size == 0)
|
|
return 0;
|
|
int n = (_free >= size) ? size : _free;
|
|
Misc.CopyMemory(data, offset, _data, _size-_free, n);
|
|
_free -= n;
|
|
return n;
|
|
}
|
|
|
|
// manually adjust the size
|
|
// used after file reads directly into a buffer
|
|
internal void AdjustSize(int size) {
|
|
_free -= size;
|
|
}
|
|
|
|
internal override void AppendEncodedChars(char[] data, int offset, int size, Encoder encoder, bool flushEncoder) {
|
|
int byteSize = UnsafeAppendEncodedChars(data, offset, size, _data, _size - _free, _free, encoder, flushEncoder);
|
|
_free -= byteSize;
|
|
#if DBG
|
|
Debug.Trace("UnmanagedBuffers", "Encoding chars, charCount=" + size + ", byteCount=" + byteSize);
|
|
#endif
|
|
}
|
|
|
|
private unsafe static int UnsafeAppendEncodedChars(char[] src, int srcOffset, int srcSize, IntPtr dest, int destOffset, int destSize, Encoder encoder, bool flushEncoder) {
|
|
int numBytes = 0;
|
|
|
|
byte* destBytes = ((byte*)dest) + destOffset;
|
|
|
|
fixed (char* charSrc = src) {
|
|
numBytes = encoder.GetBytes(charSrc+srcOffset, srcSize, destBytes, destSize, flushEncoder);
|
|
}
|
|
|
|
return numBytes;
|
|
}
|
|
|
|
//
|
|
// IHttpResponseElement implementation
|
|
//
|
|
|
|
/*
|
|
* Get number of bytes
|
|
*/
|
|
long IHttpResponseElement.GetSize() {
|
|
return (_size - _free);
|
|
}
|
|
|
|
/*
|
|
* Get bytes (for filtering)
|
|
*/
|
|
byte[] IHttpResponseElement.GetBytes() {
|
|
int n = (_size - _free);
|
|
|
|
if (n > 0) {
|
|
byte[] data = new byte[n];
|
|
Misc.CopyMemory(_data, 0, data, 0, n);
|
|
return data;
|
|
}
|
|
else {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Write HttpWorkerRequest
|
|
*/
|
|
void IHttpResponseElement.Send(HttpWorkerRequest wr) {
|
|
int n = _size - _free;
|
|
|
|
if (n > 0) {
|
|
wr.SendResponseFromMemory(_data, n, true);
|
|
}
|
|
|
|
#if DBG
|
|
Debug.Trace("UnmanagedBuffers", "Sending data, byteCount=" + n + ", freeBytes=" + _free);
|
|
#endif
|
|
}
|
|
|
|
internal unsafe IntPtr FreeLocation {
|
|
get {
|
|
int n = _size - _free;
|
|
byte * p = (byte*) _data.ToPointer();
|
|
p += n;
|
|
|
|
return new IntPtr(p);
|
|
}
|
|
}
|
|
}
|
|
|
|
#endif // !FEATURE_PAL
|
|
/*
|
|
* Response element where data comes from resource
|
|
*/
|
|
internal sealed class HttpResourceResponseElement : IHttpResponseElement {
|
|
private IntPtr _data;
|
|
private int _offset;
|
|
private int _size;
|
|
|
|
internal HttpResourceResponseElement(IntPtr data, int offset, int size) {
|
|
_data = data;
|
|
_offset = offset;
|
|
_size = size;
|
|
}
|
|
|
|
//
|
|
// IHttpResponseElement implementation
|
|
//
|
|
|
|
/*
|
|
* Get number of bytes
|
|
*/
|
|
long IHttpResponseElement.GetSize() {
|
|
return _size;
|
|
}
|
|
|
|
/*
|
|
* Get bytes (used only for filtering)
|
|
*/
|
|
byte[] IHttpResponseElement.GetBytes() {
|
|
if (_size > 0) {
|
|
byte[] data = new byte[_size];
|
|
Misc.CopyMemory(_data, _offset, data, 0, _size);
|
|
return data;
|
|
}
|
|
else {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Write HttpWorkerRequest
|
|
*/
|
|
void IHttpResponseElement.Send(HttpWorkerRequest wr) {
|
|
if (_size > 0) {
|
|
wr.SendResponseFromMemory(new IntPtr(_data.ToInt64()+_offset), _size, isBufferFromUnmanagedPool: false);
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Response element where data comes from file
|
|
*/
|
|
internal sealed class HttpFileResponseElement : IHttpResponseElement {
|
|
private String _filename;
|
|
private long _offset;
|
|
private long _size;
|
|
private bool _isImpersonating;
|
|
private bool _useTransmitFile;
|
|
|
|
/**
|
|
* Constructor from filename, uses TransmitFile
|
|
*/
|
|
internal HttpFileResponseElement(String filename, long offset, long size, bool isImpersonating, bool supportsLongTransmitFile) :
|
|
this (filename, offset, size, isImpersonating, true, supportsLongTransmitFile) {
|
|
}
|
|
|
|
/*
|
|
* Constructor from filename and range (doesn't use TransmitFile)
|
|
*/
|
|
internal HttpFileResponseElement(String filename, long offset, long size) :
|
|
this (filename, offset, size, false, false, false) {
|
|
}
|
|
|
|
private HttpFileResponseElement(string filename,
|
|
long offset,
|
|
long size,
|
|
bool isImpersonating,
|
|
bool useTransmitFile,
|
|
bool supportsLongTransmitFile)
|
|
{
|
|
if ((!supportsLongTransmitFile && size > Int32.MaxValue) || (size < 0)) {
|
|
throw new ArgumentOutOfRangeException("size", size, SR.GetString(SR.Invalid_size));
|
|
}
|
|
if ((!supportsLongTransmitFile && offset > Int32.MaxValue) || (offset < 0)) {
|
|
throw new ArgumentOutOfRangeException("offset", offset, SR.GetString(SR.Invalid_size));
|
|
}
|
|
_filename = filename;
|
|
_offset = offset;
|
|
_size = size;
|
|
_isImpersonating = isImpersonating;
|
|
_useTransmitFile = useTransmitFile;
|
|
}
|
|
|
|
|
|
internal string FileName { get { return _filename; } }
|
|
|
|
internal long Offset { get { return _offset; } }
|
|
|
|
//
|
|
// IHttpResponseElement implementation
|
|
//
|
|
|
|
/*
|
|
* Get number of bytes
|
|
*/
|
|
long IHttpResponseElement.GetSize() {
|
|
return _size;
|
|
}
|
|
|
|
/*
|
|
* Get bytes (for filtering)
|
|
*/
|
|
byte[] IHttpResponseElement.GetBytes() {
|
|
if (_size == 0)
|
|
return null;
|
|
|
|
byte[] data = null;
|
|
FileStream f = null;
|
|
|
|
try {
|
|
f = new FileStream(_filename, FileMode.Open, FileAccess.Read, FileShare.Read);
|
|
|
|
long fileSize = f.Length;
|
|
|
|
if (_offset < 0 || _size > fileSize - _offset)
|
|
throw new HttpException(SR.GetString(SR.Invalid_range));
|
|
|
|
if (_offset > 0)
|
|
f.Seek(_offset, SeekOrigin.Begin);
|
|
|
|
int intSize = (int)_size;
|
|
data = new byte[intSize];
|
|
int bytesRead = 0;
|
|
do {
|
|
int n = f.Read(data, bytesRead, intSize);
|
|
if (n == 0) {
|
|
break;
|
|
}
|
|
bytesRead += n;
|
|
intSize -= n;
|
|
} while (intSize > 0);
|
|
// Technically here, the buffer may not be full after the loop, but we choose to ignore
|
|
// this very rare condition (the file became shorter between the time we looked at its length
|
|
// and the moment we read it). In this case, we would just have a few zero bytes at the end
|
|
// of the byte[], which is fine.
|
|
}
|
|
finally {
|
|
if (f != null)
|
|
f.Close();
|
|
}
|
|
|
|
return data;
|
|
}
|
|
|
|
/*
|
|
* Write HttpWorkerRequest
|
|
*/
|
|
void IHttpResponseElement.Send(HttpWorkerRequest wr) {
|
|
if (_size > 0) {
|
|
if (_useTransmitFile) {
|
|
wr.TransmitFile(_filename, _offset, _size, _isImpersonating); // This is for IIS 6, in-proc TransmitFile
|
|
}
|
|
else {
|
|
wr.SendResponseFromFile(_filename, _offset, _size);
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
/*
|
|
* Response element for substituiton
|
|
*/
|
|
internal sealed class HttpSubstBlockResponseElement : IHttpResponseElement {
|
|
private HttpResponseSubstitutionCallback _callback;
|
|
private IHttpResponseElement _firstSubstitution;
|
|
private IntPtr _firstSubstData;
|
|
private int _firstSubstDataSize;
|
|
private bool _isIIS7WorkerRequest;
|
|
|
|
// used by OutputCache
|
|
internal HttpResponseSubstitutionCallback Callback { get { return _callback; } }
|
|
|
|
/*
|
|
* Constructor given the name and the data (fill char converted to bytes)
|
|
* holds on to the data
|
|
*/
|
|
internal HttpSubstBlockResponseElement(HttpResponseSubstitutionCallback callback, Encoding encoding, Encoder encoder, IIS7WorkerRequest iis7WorkerRequest) {
|
|
_callback = callback;
|
|
if (iis7WorkerRequest != null) {
|
|
_isIIS7WorkerRequest = true;
|
|
String s = _callback(HttpContext.Current);
|
|
if (s == null) {
|
|
throw new ArgumentNullException("substitutionString");
|
|
}
|
|
CreateFirstSubstData(s, iis7WorkerRequest, encoder);
|
|
}
|
|
else {
|
|
_firstSubstitution = Substitute(encoding);
|
|
}
|
|
}
|
|
|
|
// special constructor used by OutputCache
|
|
internal HttpSubstBlockResponseElement(HttpResponseSubstitutionCallback callback) {
|
|
_callback = callback;
|
|
}
|
|
|
|
// WOS 1926509: ASP.NET: WriteSubstitution in integrated mode needs to support callbacks that return String.Empty
|
|
private unsafe void CreateFirstSubstData(String s, IIS7WorkerRequest iis7WorkerRequest, Encoder encoder) {
|
|
Debug.Assert(s != null, "s != null");
|
|
|
|
IntPtr pbBuffer;
|
|
int numBytes = 0;
|
|
int cch = s.Length;
|
|
if (cch > 0) {
|
|
fixed (char * pch = s) {
|
|
int cbBuffer = encoder.GetByteCount(pch, cch, true /*flush*/);
|
|
pbBuffer = iis7WorkerRequest.AllocateRequestMemory(cbBuffer);
|
|
if (pbBuffer != IntPtr.Zero) {
|
|
numBytes = encoder.GetBytes(pch, cch, (byte*)pbBuffer, cbBuffer, true /*flush*/);
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
// deal with empty string
|
|
pbBuffer = iis7WorkerRequest.AllocateRequestMemory(1);
|
|
}
|
|
|
|
if (pbBuffer == IntPtr.Zero) {
|
|
throw new OutOfMemoryException();
|
|
}
|
|
_firstSubstData = pbBuffer;
|
|
_firstSubstDataSize = numBytes;
|
|
}
|
|
|
|
/*
|
|
* Performs substition -- return the resulting HttpResponseBufferElement
|
|
* holds on to the data
|
|
*/
|
|
internal IHttpResponseElement Substitute(Encoding e) {
|
|
String s = _callback(HttpContext.Current);
|
|
byte[] data = e.GetBytes(s);
|
|
return new HttpResponseBufferElement(data, data.Length);
|
|
}
|
|
|
|
internal bool PointerEquals(IntPtr ptr) {
|
|
Debug.Assert(HttpRuntime.UseIntegratedPipeline, "HttpRuntime.UseIntegratedPipeline");
|
|
return _firstSubstData == ptr;
|
|
}
|
|
|
|
//
|
|
// IHttpResponseElement implementation (doesn't do anything)
|
|
//
|
|
|
|
/*
|
|
* Get number of bytes
|
|
*/
|
|
long IHttpResponseElement.GetSize() {
|
|
if (_isIIS7WorkerRequest) {
|
|
return _firstSubstDataSize;
|
|
}
|
|
else {
|
|
return _firstSubstitution.GetSize();
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Get bytes (for filtering)
|
|
*/
|
|
byte[] IHttpResponseElement.GetBytes() {
|
|
if (_isIIS7WorkerRequest) {
|
|
if (_firstSubstDataSize > 0) {
|
|
byte[] data = new byte[_firstSubstDataSize];
|
|
Misc.CopyMemory(_firstSubstData, 0, data, 0, _firstSubstDataSize);
|
|
return data;
|
|
}
|
|
else {
|
|
// WOS 1926509: ASP.NET: WriteSubstitution in integrated mode needs to support callbacks that return String.Empty
|
|
return (_firstSubstData == IntPtr.Zero) ? null : new byte[0];
|
|
}
|
|
}
|
|
else {
|
|
return _firstSubstitution.GetBytes();
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Write HttpWorkerRequest
|
|
*/
|
|
void IHttpResponseElement.Send(HttpWorkerRequest wr) {
|
|
if (_isIIS7WorkerRequest) {
|
|
IIS7WorkerRequest iis7WorkerRequest = wr as IIS7WorkerRequest;
|
|
if (iis7WorkerRequest != null) {
|
|
// buffer can have size of zero if the subst block is an emptry string
|
|
iis7WorkerRequest.SendResponseFromIISAllocatedRequestMemory(_firstSubstData, _firstSubstDataSize);
|
|
}
|
|
}
|
|
else {
|
|
_firstSubstitution.Send(wr);
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Stream object synchronized with Writer
|
|
*/
|
|
internal class HttpResponseStream : Stream {
|
|
private HttpWriter _writer;
|
|
|
|
internal HttpResponseStream(HttpWriter writer) {
|
|
_writer = writer;
|
|
}
|
|
|
|
//
|
|
// Public Stream method implementations
|
|
//
|
|
|
|
public override bool CanRead {
|
|
get { return false;}
|
|
}
|
|
|
|
public override bool CanSeek {
|
|
get { return false;}
|
|
}
|
|
|
|
public override bool CanWrite {
|
|
get { return true;}
|
|
}
|
|
|
|
public override long Length {
|
|
get {throw new NotSupportedException();}
|
|
}
|
|
|
|
public override long Position {
|
|
get {throw new NotSupportedException();}
|
|
|
|
set {throw new NotSupportedException();}
|
|
}
|
|
|
|
protected override void Dispose(bool disposing) {
|
|
try {
|
|
if (disposing)
|
|
_writer.Close();
|
|
}
|
|
finally {
|
|
base.Dispose(disposing);
|
|
}
|
|
}
|
|
|
|
public override void Flush() {
|
|
_writer.Flush();
|
|
}
|
|
|
|
public override long Seek(long offset, SeekOrigin origin) {
|
|
throw new NotSupportedException();
|
|
}
|
|
|
|
public override void SetLength(long value) {
|
|
throw new NotSupportedException();
|
|
}
|
|
|
|
public override int Read(byte[] buffer, int offset, int count) {
|
|
throw new NotSupportedException();
|
|
}
|
|
|
|
public override void Write(byte[] buffer, int offset, int count) {
|
|
if (_writer.IgnoringFurtherWrites) {
|
|
return;
|
|
}
|
|
|
|
// Dev10 Bug 507392: Do as Stream does.
|
|
if (buffer == null)
|
|
throw new ArgumentNullException("buffer");
|
|
if (offset < 0)
|
|
throw new ArgumentOutOfRangeException("offset");
|
|
if (count < 0)
|
|
throw new ArgumentOutOfRangeException("count");
|
|
if (buffer.Length - offset < count)
|
|
throw new ArgumentException(SR.GetString(SR.InvalidOffsetOrCount, "offset", "count"));
|
|
if (count == 0)
|
|
return;
|
|
|
|
_writer.WriteFromStream(buffer, offset, count);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Stream serving as sink for filters
|
|
*/
|
|
internal sealed class HttpResponseStreamFilterSink : HttpResponseStream {
|
|
private bool _filtering = false;
|
|
|
|
internal HttpResponseStreamFilterSink(HttpWriter writer) : base(writer) {
|
|
}
|
|
|
|
private void VerifyState() {
|
|
// throw exception on unexpected filter writes
|
|
|
|
if (!_filtering)
|
|
throw new HttpException(SR.GetString(SR.Invalid_use_of_response_filter));
|
|
}
|
|
|
|
internal bool Filtering {
|
|
get { return _filtering;}
|
|
set { _filtering = value;}
|
|
}
|
|
|
|
//
|
|
// Stream methods just go to the base class with exception of Close and Flush that do nothing
|
|
//
|
|
|
|
protected override void Dispose(bool disposing) {
|
|
// do nothing
|
|
base.Dispose(disposing);
|
|
}
|
|
|
|
public override void Flush() {
|
|
// do nothing (this is not a buffering stream)
|
|
}
|
|
|
|
public override void Write(byte[] buffer, int offset, int count) {
|
|
VerifyState();
|
|
base.Write(buffer, offset, count);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* TextWriter synchronized with the response object
|
|
*/
|
|
|
|
/// <devdoc>
|
|
/// <para>A TextWriter class synchronized with the Response object.</para>
|
|
/// </devdoc>
|
|
public sealed class HttpWriter : TextWriter {
|
|
private HttpResponse _response;
|
|
private HttpResponseStream _stream;
|
|
|
|
private HttpResponseStreamFilterSink _filterSink; // sink stream for the filter writes
|
|
private Stream _installedFilter; // installed filtering stream
|
|
|
|
private HttpBaseMemoryResponseBufferElement _lastBuffer;
|
|
private ArrayList _buffers;
|
|
|
|
private char[] _charBuffer;
|
|
private int _charBufferLength;
|
|
private int _charBufferFree;
|
|
private ArrayList _substElements = null;
|
|
|
|
static IAllocatorProvider s_DefaultAllocator = null;
|
|
IAllocatorProvider _allocator = null; // Use only via HttpWriter.AllocationProvider to ensure proper fallback
|
|
|
|
// cached data from the response
|
|
// can be invalidated via UpdateResponseXXX methods
|
|
|
|
private bool _responseBufferingOn;
|
|
private Encoding _responseEncoding;
|
|
private bool _responseEncodingUsed;
|
|
private bool _responseEncodingUpdated;
|
|
private Encoder _responseEncoder;
|
|
private int _responseCodePage;
|
|
private bool _responseCodePageIsAsciiCompat;
|
|
|
|
private bool _ignoringFurtherWrites;
|
|
|
|
private bool _hasBeenClearedRecently;
|
|
|
|
internal HttpWriter(HttpResponse response): base(null) {
|
|
_response = response;
|
|
_stream = new HttpResponseStream(this);
|
|
|
|
_buffers = new ArrayList();
|
|
_lastBuffer = null;
|
|
|
|
// Setup the buffer on demand using CharBuffer property
|
|
_charBuffer = null;
|
|
_charBufferLength = 0;
|
|
_charBufferFree = 0;
|
|
|
|
UpdateResponseBuffering();
|
|
|
|
// delay getting response encoding until it is really needed
|
|
// UpdateResponseEncoding();
|
|
}
|
|
|
|
internal ArrayList SubstElements {
|
|
get {
|
|
if (_substElements == null) {
|
|
_substElements = new ArrayList();
|
|
// dynamic compression is not compatible with post cache substitution
|
|
_response.Context.Request.SetDynamicCompression(false /*enable*/);
|
|
}
|
|
return _substElements;
|
|
}
|
|
}
|
|
|
|
/// <devdov>
|
|
/// True if the writer is ignoring all writes
|
|
/// </devdoc>
|
|
internal bool IgnoringFurtherWrites {
|
|
get {
|
|
return _ignoringFurtherWrites;
|
|
}
|
|
}
|
|
|
|
/// <devdov>
|
|
/// </devdoc>
|
|
internal void IgnoreFurtherWrites() {
|
|
_ignoringFurtherWrites = true;
|
|
}
|
|
|
|
internal void UpdateResponseBuffering() {
|
|
_responseBufferingOn = _response.BufferOutput;
|
|
}
|
|
|
|
internal void UpdateResponseEncoding() {
|
|
if (_responseEncodingUpdated) { // subsequent update
|
|
if (_charBufferLength != _charBufferFree)
|
|
FlushCharBuffer(true);
|
|
}
|
|
|
|
_responseEncoding = _response.ContentEncoding;
|
|
_responseEncoder = _response.ContentEncoder;
|
|
_responseCodePage = _responseEncoding.CodePage;
|
|
_responseCodePageIsAsciiCompat = CodePageUtils.IsAsciiCompatibleCodePage(_responseCodePage);
|
|
_responseEncodingUpdated = true;
|
|
}
|
|
|
|
|
|
/// <devdoc>
|
|
/// <para>[To be supplied.]</para>
|
|
/// </devdoc>
|
|
public override Encoding Encoding {
|
|
get {
|
|
if (!_responseEncodingUpdated) {
|
|
UpdateResponseEncoding();
|
|
}
|
|
|
|
return _responseEncoding;
|
|
}
|
|
}
|
|
|
|
internal Encoder Encoder {
|
|
get {
|
|
if (!_responseEncodingUpdated) {
|
|
UpdateResponseEncoding();
|
|
}
|
|
return _responseEncoder;
|
|
}
|
|
}
|
|
|
|
private HttpBaseMemoryResponseBufferElement CreateNewMemoryBufferElement() {
|
|
return new HttpResponseUnmanagedBufferElement(); /* using unmanaged buffers */
|
|
}
|
|
|
|
internal void DisposeIntegratedBuffers() {
|
|
Debug.Assert(HttpRuntime.UseIntegratedPipeline);
|
|
|
|
// don't recycle char buffers here (ClearBuffers will)
|
|
// do recycle native output buffers
|
|
if (_buffers != null) {
|
|
|
|
int n = _buffers.Count;
|
|
for (int i = 0; i < n; i++) {
|
|
HttpBaseMemoryResponseBufferElement buf = _buffers[i] as HttpBaseMemoryResponseBufferElement;
|
|
|
|
// if this is a native buffer, this will bump down the ref count
|
|
// the native side also keeps a ref count (see mgdhandler.cxx)
|
|
if (buf != null) {
|
|
buf.Recycle();
|
|
}
|
|
}
|
|
|
|
_buffers = null;
|
|
}
|
|
|
|
// finish by clearing buffers
|
|
ClearBuffers();
|
|
}
|
|
|
|
internal void RecycleBuffers() {
|
|
// recycle char buffers
|
|
|
|
if (_charBuffer != null) {
|
|
AllocatorProvider.CharBufferAllocator.ReuseBuffer(_charBuffer);
|
|
_charBuffer = null;
|
|
}
|
|
|
|
// recycle output buffers
|
|
RecycleBufferElements();
|
|
}
|
|
|
|
internal static void ReleaseAllPooledBuffers() {
|
|
if (s_DefaultAllocator != null) {
|
|
s_DefaultAllocator.TrimMemory();
|
|
}
|
|
}
|
|
|
|
internal void ClearSubstitutionBlocks() {
|
|
_substElements = null;
|
|
}
|
|
|
|
internal IAllocatorProvider AllocatorProvider {
|
|
private get {
|
|
if (_allocator == null) {
|
|
if (s_DefaultAllocator == null) {
|
|
// Create default static allocator
|
|
IBufferAllocator charAllocator = new CharBufferAllocator(BufferingParams.CHAR_BUFFER_SIZE, BufferingParams.MAX_FREE_CHAR_BUFFERS);
|
|
|
|
AllocatorProvider alloc = new AllocatorProvider();
|
|
alloc.CharBufferAllocator = new BufferAllocatorWrapper<char>(charAllocator);
|
|
|
|
Interlocked.CompareExchange(ref s_DefaultAllocator, alloc, null);
|
|
}
|
|
|
|
_allocator = s_DefaultAllocator;
|
|
}
|
|
|
|
return _allocator;
|
|
}
|
|
|
|
set {
|
|
_allocator = value;
|
|
}
|
|
}
|
|
|
|
private void RecycleBufferElements() {
|
|
if (_buffers != null) {
|
|
|
|
int n = _buffers.Count;
|
|
for (int i = 0; i < n; i++) {
|
|
HttpBaseMemoryResponseBufferElement buf = _buffers[i] as HttpBaseMemoryResponseBufferElement;
|
|
if (buf != null) {
|
|
buf.Recycle();
|
|
}
|
|
}
|
|
|
|
_buffers = null;
|
|
}
|
|
}
|
|
|
|
private void ClearCharBuffer() {
|
|
_charBufferFree = _charBufferLength;
|
|
}
|
|
|
|
private char[] CharBuffer {
|
|
get {
|
|
if (_charBuffer == null) {
|
|
_charBuffer = AllocatorProvider.CharBufferAllocator.GetBuffer();
|
|
|
|
_charBufferLength = _charBuffer.Length;
|
|
_charBufferFree = _charBufferLength;
|
|
}
|
|
|
|
return _charBuffer;
|
|
}
|
|
}
|
|
|
|
private void FlushCharBuffer(bool flushEncoder) {
|
|
int numChars = _charBufferLength - _charBufferFree;
|
|
|
|
Debug.Assert(numChars > 0);
|
|
|
|
// remember that response required encoding (to indicate the charset= is needed)
|
|
if (!_responseEncodingUpdated) {
|
|
UpdateResponseEncoding();
|
|
}
|
|
|
|
_responseEncodingUsed = true;
|
|
|
|
// estimate conversion size
|
|
int estByteSize = _responseEncoding.GetMaxByteCount(numChars);
|
|
|
|
if (estByteSize <= BufferingParams.MAX_BYTES_TO_COPY || !_responseBufferingOn) {
|
|
// small size -- allocate intermediate buffer and copy into the output buffer
|
|
byte[] byteBuffer = new byte[estByteSize];
|
|
int realByteSize = _responseEncoder.GetBytes(CharBuffer, 0, numChars,
|
|
byteBuffer, 0, flushEncoder);
|
|
BufferData(byteBuffer, 0, realByteSize, false);
|
|
}
|
|
else {
|
|
// convert right into the output buffer
|
|
|
|
int free = (_lastBuffer != null) ? _lastBuffer.FreeBytes : 0;
|
|
|
|
if (free < estByteSize) {
|
|
// need new buffer -- last one doesn't have enough space
|
|
_lastBuffer = CreateNewMemoryBufferElement();
|
|
_buffers.Add(_lastBuffer);
|
|
free = _lastBuffer.FreeBytes;
|
|
}
|
|
|
|
// byte buffers must be long enough to keep everything in char buffer
|
|
Debug.Assert(free >= estByteSize);
|
|
_lastBuffer.AppendEncodedChars(CharBuffer, 0, numChars, _responseEncoder, flushEncoder);
|
|
}
|
|
|
|
_charBufferFree = _charBufferLength;
|
|
}
|
|
|
|
private void BufferData(byte[] data, int offset, int size, bool needToCopyData) {
|
|
int n;
|
|
|
|
// try last buffer
|
|
if (_lastBuffer != null) {
|
|
n = _lastBuffer.Append(data, offset, size);
|
|
size -= n;
|
|
offset += n;
|
|
}
|
|
else if (!needToCopyData && offset == 0 && !_responseBufferingOn) {
|
|
// when not buffering, there is no need for big buffer accumulating multiple writes
|
|
// the byte[] data can be sent as is
|
|
|
|
_buffers.Add(new HttpResponseBufferElement(data, size));
|
|
return;
|
|
}
|
|
|
|
// do other buffers if needed
|
|
while (size > 0) {
|
|
_lastBuffer = CreateNewMemoryBufferElement();
|
|
_buffers.Add(_lastBuffer);
|
|
n = _lastBuffer.Append(data, offset, size);
|
|
offset += n;
|
|
size -= n;
|
|
}
|
|
}
|
|
|
|
private void BufferResource(IntPtr data, int offset, int size) {
|
|
if (size > BufferingParams.MAX_RESOURCE_BYTES_TO_COPY || !_responseBufferingOn) {
|
|
// for long response strings create its own buffer element to avoid copy cost
|
|
// also, when not buffering, no need for an extra copy (nothing will get accumulated anyway)
|
|
_lastBuffer = null;
|
|
_buffers.Add(new HttpResourceResponseElement(data, offset, size));
|
|
return;
|
|
}
|
|
|
|
int n;
|
|
|
|
// try last buffer
|
|
if (_lastBuffer != null) {
|
|
n = _lastBuffer.Append(data, offset, size);
|
|
size -= n;
|
|
offset += n;
|
|
}
|
|
|
|
// do other buffers if needed
|
|
while (size > 0) {
|
|
_lastBuffer = CreateNewMemoryBufferElement();
|
|
_buffers.Add(_lastBuffer);
|
|
n = _lastBuffer.Append(data, offset, size);
|
|
offset += n;
|
|
size -= n;
|
|
}
|
|
}
|
|
|
|
//
|
|
// 'Write' methods to be called from other internal classes
|
|
//
|
|
|
|
internal void WriteFromStream(byte[] data, int offset, int size) {
|
|
if (_charBufferLength != _charBufferFree)
|
|
FlushCharBuffer(true);
|
|
|
|
BufferData(data, offset, size, true);
|
|
|
|
if (!_responseBufferingOn)
|
|
_response.Flush();
|
|
}
|
|
|
|
internal void WriteUTF8ResourceString(IntPtr pv, int offset, int size, bool asciiOnly) {
|
|
|
|
if (!_responseEncodingUpdated) {
|
|
UpdateResponseEncoding();
|
|
}
|
|
|
|
if (_responseCodePage == CodePageUtils.CodePageUT8 || // response encoding is UTF8
|
|
(asciiOnly && _responseCodePageIsAsciiCompat)) { // ASCII resource and ASCII-compat encoding
|
|
|
|
_responseEncodingUsed = true; // note the we used encoding (means that we need to generate charset=) see RAID#93415
|
|
|
|
// write bytes directly
|
|
if (_charBufferLength != _charBufferFree)
|
|
FlushCharBuffer(true);
|
|
|
|
BufferResource(pv, offset, size);
|
|
|
|
if (!_responseBufferingOn)
|
|
_response.Flush();
|
|
}
|
|
else {
|
|
// have to re-encode with response's encoding -- use public Write(String)
|
|
Write(StringResourceManager.ResourceToString(pv, offset, size));
|
|
}
|
|
}
|
|
|
|
internal void TransmitFile(string filename, long offset, long size, bool isImpersonating, bool supportsLongTransmitFile) {
|
|
if (_charBufferLength != _charBufferFree)
|
|
FlushCharBuffer(true);
|
|
|
|
_lastBuffer = null;
|
|
_buffers.Add(new HttpFileResponseElement(filename, offset, size, isImpersonating, supportsLongTransmitFile));
|
|
|
|
if (!_responseBufferingOn)
|
|
_response.Flush();
|
|
}
|
|
|
|
internal void WriteFile(String filename, long offset, long size) {
|
|
if (_charBufferLength != _charBufferFree)
|
|
FlushCharBuffer(true);
|
|
|
|
_lastBuffer = null;
|
|
_buffers.Add(new HttpFileResponseElement(filename, offset, size));
|
|
|
|
if (!_responseBufferingOn)
|
|
_response.Flush();
|
|
}
|
|
|
|
//
|
|
// Support for substitution blocks
|
|
//
|
|
|
|
internal void WriteSubstBlock(HttpResponseSubstitutionCallback callback, IIS7WorkerRequest iis7WorkerRequest) {
|
|
if (_charBufferLength != _charBufferFree)
|
|
FlushCharBuffer(true);
|
|
_lastBuffer = null;
|
|
|
|
// add new substitution block to the buffer list
|
|
IHttpResponseElement element = new HttpSubstBlockResponseElement(callback, Encoding, Encoder, iis7WorkerRequest);
|
|
_buffers.Add(element);
|
|
|
|
if (iis7WorkerRequest != null) {
|
|
SubstElements.Add(element);
|
|
}
|
|
|
|
if (!_responseBufferingOn)
|
|
_response.Flush();
|
|
}
|
|
|
|
//
|
|
// Support for response buffer manipulation: HasBeenClearedRecently, GetResponseBufferCountAfterFlush,
|
|
// and MoveResponseBufferRangeForward. The intended use of these functions is to rearrange
|
|
// the order of the buffers. Improper use of these functions may result in excessive memory use.
|
|
// They were added specifically so that custom hidden form data could be moved to the beginning
|
|
// of the form.
|
|
|
|
internal bool HasBeenClearedRecently {
|
|
get {
|
|
return _hasBeenClearedRecently;
|
|
}
|
|
set {
|
|
_hasBeenClearedRecently = value;
|
|
}
|
|
}
|
|
|
|
// Gets the response buffer count after flushing the char buffer. Note that _lastBuffer is cleared,
|
|
// and therefore may not be filled, so calling this can lead to inefficient use of response buffers.
|
|
internal int GetResponseBufferCountAfterFlush() {
|
|
if (_charBufferLength != _charBufferFree) {
|
|
FlushCharBuffer(true);
|
|
}
|
|
|
|
// set _lastBuffer to null to prevent more data from being added to it
|
|
_lastBuffer = null;
|
|
|
|
return _buffers.Count;
|
|
}
|
|
|
|
// Move the specified range of buffers forward in the buffer list.
|
|
internal void MoveResponseBufferRangeForward(int srcIndex, int srcCount, int dstIndex) {
|
|
Debug.Assert(dstIndex <= srcIndex);
|
|
|
|
// DevDiv Bugs 154630: No need to copy the form between temporary array and the buffer list when
|
|
// no hidden fields are written.
|
|
if (srcCount > 0) {
|
|
// create temporary storage for buffers that will be moved backwards
|
|
object[] temp = new object[srcIndex - dstIndex];
|
|
|
|
// copy buffers that will be moved backwards
|
|
_buffers.CopyTo(dstIndex, temp, 0, temp.Length);
|
|
|
|
// move the range forward from srcIndex to dstIndex
|
|
for (int i = 0; i < srcCount; i++) {
|
|
_buffers[dstIndex + i] = _buffers[srcIndex + i];
|
|
}
|
|
|
|
// insert buffers that were placed in temporary storage
|
|
for (int i = 0; i < temp.Length; i++) {
|
|
_buffers[dstIndex + srcCount + i] = temp[i];
|
|
}
|
|
}
|
|
|
|
// set _lastBuffer
|
|
HttpBaseMemoryResponseBufferElement buf = _buffers[_buffers.Count-1] as HttpBaseMemoryResponseBufferElement;
|
|
if (buf != null && buf.FreeBytes > 0) {
|
|
_lastBuffer = buf;
|
|
}
|
|
else {
|
|
_lastBuffer = null;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Buffer management
|
|
//
|
|
|
|
internal void ClearBuffers() {
|
|
ClearCharBuffer();
|
|
|
|
// re-enable dynamic compression if we are about to clear substitution blocks
|
|
if (_substElements != null) {
|
|
_response.Context.Request.SetDynamicCompression(true /*enable*/);
|
|
}
|
|
|
|
//VSWhidbey 559434: Private Bytes goes thru roof because unmanaged buffers are not recycled when Response.Flush is called
|
|
RecycleBufferElements();
|
|
|
|
_buffers = new ArrayList();
|
|
_lastBuffer = null;
|
|
_hasBeenClearedRecently = true;
|
|
}
|
|
|
|
internal long GetBufferedLength() {
|
|
if (_charBufferLength != _charBufferFree)
|
|
FlushCharBuffer(true);
|
|
|
|
long size = 0;
|
|
if (_buffers != null) {
|
|
int n = _buffers.Count;
|
|
for (int i = 0; i < n; i++) {
|
|
size += ((IHttpResponseElement)_buffers[i]).GetSize();
|
|
}
|
|
}
|
|
return size;
|
|
}
|
|
|
|
internal bool ResponseEncodingUsed {
|
|
get { return _responseEncodingUsed; }
|
|
}
|
|
|
|
// in integrated mode, snapshots need to pull the chunks from the IIS
|
|
// buffers since they may have already been pushed through
|
|
// Therefore, we can't rely solely on what's in the HttpWriter
|
|
// at the moment
|
|
internal ArrayList GetIntegratedSnapshot(out bool hasSubstBlocks, IIS7WorkerRequest wr) {
|
|
ArrayList buffers = null;
|
|
|
|
// first, get what's in our buffers
|
|
ArrayList writerBuffers = GetSnapshot(out hasSubstBlocks);
|
|
|
|
// now, get what's in the IIS buffers
|
|
ArrayList nativeBuffers = wr.GetBufferedResponseChunks(true, _substElements, ref hasSubstBlocks);
|
|
|
|
// try to append the current buffers to what we just
|
|
// got from the native buffer
|
|
if (null != nativeBuffers) {
|
|
for (int i = 0; i < writerBuffers.Count; i++) {
|
|
nativeBuffers.Add(writerBuffers[i]);
|
|
}
|
|
buffers = nativeBuffers;
|
|
}
|
|
else {
|
|
buffers = writerBuffers;
|
|
}
|
|
|
|
// if we have substitution blocks:
|
|
// 1) throw exception if someone modified the subst blocks
|
|
// 2) re-enable compression
|
|
if (_substElements != null && _substElements.Count > 0) {
|
|
int substCount = 0;
|
|
// scan buffers for subst blocks
|
|
for(int i = 0; i < buffers.Count; i++) {
|
|
if (buffers[i] is HttpSubstBlockResponseElement) {
|
|
substCount++;
|
|
if (substCount == _substElements.Count) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (substCount != _substElements.Count) {
|
|
throw new InvalidOperationException(SR.GetString(SR.Substitution_blocks_cannot_be_modified));
|
|
}
|
|
|
|
// re-enable dynamic compression when we have a snapshot of the subst blocks.
|
|
_response.Context.Request.SetDynamicCompression(true /*enable*/);
|
|
}
|
|
|
|
return buffers;
|
|
}
|
|
|
|
//
|
|
// Snapshot for caching
|
|
//
|
|
|
|
internal ArrayList GetSnapshot(out bool hasSubstBlocks) {
|
|
if (_charBufferLength != _charBufferFree)
|
|
FlushCharBuffer(true);
|
|
|
|
_lastBuffer = null; // to make sure nothing gets appended after
|
|
|
|
hasSubstBlocks = false;
|
|
|
|
ArrayList buffers = new ArrayList();
|
|
|
|
// copy buffer references to the returned list, make non-recyclable
|
|
int n = _buffers.Count;
|
|
for (int i = 0; i < n; i++) {
|
|
Object responseElement = _buffers[i];
|
|
|
|
HttpBaseMemoryResponseBufferElement buffer = responseElement as HttpBaseMemoryResponseBufferElement;
|
|
|
|
if (buffer != null) {
|
|
if (buffer.FreeBytes > BufferingParams.MAX_FREE_BYTES_TO_CACHE) {
|
|
// copy data if too much is free
|
|
responseElement = buffer.Clone();
|
|
}
|
|
else {
|
|
// cache the buffer as is with free bytes
|
|
buffer.DisableRecycling();
|
|
}
|
|
}
|
|
else if (responseElement is HttpSubstBlockResponseElement) {
|
|
hasSubstBlocks = true;
|
|
}
|
|
|
|
buffers.Add(responseElement);
|
|
}
|
|
return buffers;
|
|
}
|
|
|
|
internal void UseSnapshot(ArrayList buffers) {
|
|
ClearBuffers();
|
|
|
|
// copy buffer references to the internal buffer list
|
|
// make substitution if needed
|
|
|
|
int n = buffers.Count;
|
|
for (int i = 0; i < n; i++) {
|
|
Object responseElement = buffers[i];
|
|
HttpSubstBlockResponseElement substBlock = (responseElement as HttpSubstBlockResponseElement);
|
|
|
|
if (substBlock != null) {
|
|
_buffers.Add(substBlock.Substitute(Encoding));
|
|
}
|
|
else {
|
|
_buffers.Add(responseElement);
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// Support for response stream filters
|
|
//
|
|
|
|
internal Stream GetCurrentFilter() {
|
|
if (_installedFilter != null)
|
|
return _installedFilter;
|
|
|
|
if (_filterSink == null)
|
|
_filterSink = new HttpResponseStreamFilterSink(this);
|
|
|
|
return _filterSink;
|
|
}
|
|
|
|
internal bool FilterInstalled {
|
|
get { return (_installedFilter != null); }
|
|
}
|
|
|
|
internal void InstallFilter(Stream filter) {
|
|
if (_filterSink == null) // have to redirect to the sink -- null means sink wasn't ever asked for
|
|
throw new HttpException(SR.GetString(SR.Invalid_response_filter));
|
|
|
|
_installedFilter = filter;
|
|
}
|
|
|
|
internal void Filter(bool finalFiltering) {
|
|
// no filter?
|
|
if (_installedFilter == null)
|
|
return;
|
|
|
|
// flush char buffer and remember old buffers
|
|
if (_charBufferLength != _charBufferFree)
|
|
FlushCharBuffer(true);
|
|
|
|
_lastBuffer = null;
|
|
|
|
// no content to filter
|
|
// Allow the filter to be closed (Dev10 Bug 550168).
|
|
if (_buffers.Count == 0 && !finalFiltering)
|
|
return;
|
|
|
|
// remember old buffers
|
|
ArrayList oldBuffers = _buffers;
|
|
_buffers = new ArrayList();
|
|
|
|
// push old buffer list through the filter
|
|
|
|
Debug.Assert(_filterSink != null);
|
|
|
|
_filterSink.Filtering = true;
|
|
|
|
try {
|
|
int n = oldBuffers.Count;
|
|
for (int i = 0; i < n; i++) {
|
|
IHttpResponseElement buf = (IHttpResponseElement)oldBuffers[i];
|
|
|
|
long len = buf.GetSize();
|
|
|
|
if (len > 0) {
|
|
// Convert.ToInt32 will throw for sizes larger than Int32.MaxValue.
|
|
// Filtering large response sizes is not supported
|
|
_installedFilter.Write(buf.GetBytes(), 0, Convert.ToInt32(len));
|
|
}
|
|
}
|
|
|
|
_installedFilter.Flush();
|
|
|
|
}
|
|
finally {
|
|
try {
|
|
if (finalFiltering)
|
|
_installedFilter.Close();
|
|
}
|
|
finally {
|
|
_filterSink.Filtering = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
internal void FilterIntegrated(bool finalFiltering, IIS7WorkerRequest wr) {
|
|
// no filter?
|
|
if (_installedFilter == null)
|
|
return;
|
|
|
|
// flush char buffer and remember old buffers
|
|
if (_charBufferLength != _charBufferFree)
|
|
FlushCharBuffer(true);
|
|
|
|
_lastBuffer = null;
|
|
|
|
// ISAPI mode bails if it has no buffers
|
|
// to filter, in integrated mode we need
|
|
// to check the unified response buffers
|
|
// maintained by IIS for content, as well
|
|
|
|
// remember current buffers (if any) that might be
|
|
// response entity from this transition
|
|
// (not yet pushed through to IIS response buffers)
|
|
ArrayList oldBuffers = _buffers;
|
|
_buffers = new ArrayList();
|
|
|
|
// now, get what's in the IIS buffers
|
|
ArrayList nativeBuffers = null;
|
|
bool fDummy = false;
|
|
nativeBuffers = wr.GetBufferedResponseChunks(false, null, ref fDummy);
|
|
|
|
Debug.Assert(_filterSink != null);
|
|
_filterSink.Filtering = true;
|
|
|
|
try {
|
|
// push buffers through installed filters
|
|
// push the IIS ones through first since we need to maintain order
|
|
if (null != nativeBuffers) {
|
|
for (int i = 0; i < nativeBuffers.Count; i++) {
|
|
IHttpResponseElement buf = (IHttpResponseElement)nativeBuffers[i];
|
|
|
|
long len = buf.GetSize();
|
|
|
|
if (len > 0)
|
|
_installedFilter.Write(buf.GetBytes(), 0, Convert.ToInt32(len));
|
|
|
|
}
|
|
|
|
// if we had stuff there, we now need to clear it since we may have
|
|
// transformed it
|
|
wr.ClearResponse(true /* entity */, false /* headers */);
|
|
}
|
|
|
|
// current buffers, if any
|
|
if (null != oldBuffers) {
|
|
for (int i = 0; i < oldBuffers.Count; i++) {
|
|
IHttpResponseElement buf = (IHttpResponseElement)oldBuffers[i];
|
|
|
|
long len = buf.GetSize();
|
|
|
|
if (len > 0)
|
|
_installedFilter.Write(buf.GetBytes(), 0, Convert.ToInt32(len));
|
|
|
|
}
|
|
}
|
|
|
|
_installedFilter.Flush();
|
|
}
|
|
finally {
|
|
try {
|
|
if (finalFiltering)
|
|
_installedFilter.Close();
|
|
}
|
|
finally {
|
|
_filterSink.Filtering = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// Send via worker request
|
|
//
|
|
|
|
internal void Send(HttpWorkerRequest wr) {
|
|
if (_charBufferLength != _charBufferFree)
|
|
FlushCharBuffer(true);
|
|
|
|
int n = _buffers.Count;
|
|
|
|
if (n > 0) {
|
|
// write data
|
|
for (int i = 0; i < n; i++) {
|
|
((IHttpResponseElement)_buffers[i]).Send(wr);
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// Public TextWriter method implementations
|
|
//
|
|
|
|
|
|
/// <devdoc>
|
|
/// <para> Sends all buffered output to the client and closes the socket connection.</para>
|
|
/// </devdoc>
|
|
public override void Close() {
|
|
// don't do anything (this could called from a wrapping text writer)
|
|
}
|
|
|
|
|
|
/// <devdoc>
|
|
/// <para> Sends all buffered output to the client.</para>
|
|
/// </devdoc>
|
|
public override void Flush() {
|
|
// don't flush the response
|
|
}
|
|
|
|
|
|
/// <devdoc>
|
|
/// <para> Sends a character to the client.</para>
|
|
/// </devdoc>
|
|
public override void Write(char ch) {
|
|
if (_ignoringFurtherWrites) {
|
|
return;
|
|
}
|
|
|
|
char[] buffer = CharBuffer;
|
|
|
|
if (_charBufferFree == 0) {
|
|
FlushCharBuffer(false);
|
|
}
|
|
|
|
buffer[_charBufferLength - _charBufferFree] = ch;
|
|
_charBufferFree--;
|
|
|
|
if (!_responseBufferingOn) {
|
|
_response.Flush();
|
|
}
|
|
}
|
|
|
|
|
|
/// <devdoc>
|
|
/// <para> Sends a stream of buffered characters to the client
|
|
/// using starting position and number of characters to send. </para>
|
|
/// </devdoc>
|
|
public override void Write(char[] buffer, int index, int count) {
|
|
if (_ignoringFurtherWrites) {
|
|
return;
|
|
}
|
|
|
|
// Dev10 Bug 507392: Do as TextWriter does.
|
|
if (buffer == null)
|
|
throw new ArgumentNullException("buffer");
|
|
if (index < 0)
|
|
throw new ArgumentOutOfRangeException("index");
|
|
if (count < 0)
|
|
throw new ArgumentOutOfRangeException("count");
|
|
if (buffer.Length - index < count)
|
|
throw new ArgumentException(SR.GetString(SR.InvalidOffsetOrCount, "index", "count"));
|
|
if (count == 0)
|
|
return;
|
|
|
|
char[] charBuffer = CharBuffer;
|
|
|
|
while (count > 0) {
|
|
if (_charBufferFree == 0) {
|
|
FlushCharBuffer(false);
|
|
}
|
|
|
|
int n = (count < _charBufferFree) ? count : _charBufferFree;
|
|
System.Array.Copy(buffer, index, charBuffer, _charBufferLength - _charBufferFree, n);
|
|
_charBufferFree -= n;
|
|
index += n;
|
|
count -= n;
|
|
}
|
|
|
|
if (!_responseBufferingOn) {
|
|
_response.Flush();
|
|
}
|
|
}
|
|
|
|
|
|
/// <devdoc>
|
|
/// <para>Sends a string to the client.</para>
|
|
/// </devdoc>
|
|
public override void Write(String s) {
|
|
if (_ignoringFurtherWrites)
|
|
return;
|
|
|
|
if (s == null)
|
|
return;
|
|
|
|
char[] buffer = CharBuffer;
|
|
|
|
if (s.Length == 0) {
|
|
// Ensure flush if string is empty
|
|
}
|
|
else if (s.Length < _charBufferFree) {
|
|
// fast path - 99% of string writes will not overrun the buffer
|
|
// avoid redundant arg checking in string.CopyTo
|
|
StringUtil.UnsafeStringCopy(s, 0, buffer, _charBufferLength - _charBufferFree, s.Length);
|
|
_charBufferFree -= s.Length;
|
|
}
|
|
else {
|
|
int count = s.Length;
|
|
int index = 0;
|
|
int n;
|
|
|
|
while (count > 0) {
|
|
if (_charBufferFree == 0) {
|
|
FlushCharBuffer(false);
|
|
}
|
|
|
|
n = (count < _charBufferFree) ? count : _charBufferFree;
|
|
|
|
// avoid redundant arg checking in string.CopyTo
|
|
StringUtil.UnsafeStringCopy(s, index, buffer, _charBufferLength - _charBufferFree, n);
|
|
|
|
_charBufferFree -= n;
|
|
index += n;
|
|
count -= n;
|
|
}
|
|
}
|
|
|
|
if (!_responseBufferingOn) {
|
|
_response.Flush();
|
|
}
|
|
}
|
|
|
|
|
|
/// <devdoc>
|
|
/// <para>Sends a string or a sub-string to the client.</para>
|
|
/// </devdoc>
|
|
public void WriteString(String s, int index, int count) {
|
|
if (s == null)
|
|
return;
|
|
|
|
if (index < 0) {
|
|
throw new ArgumentOutOfRangeException("index");
|
|
}
|
|
|
|
if (count < 0) {
|
|
throw new ArgumentOutOfRangeException("count");
|
|
}
|
|
|
|
if (index + count > s.Length) {
|
|
throw new ArgumentOutOfRangeException("index");
|
|
}
|
|
|
|
if (_ignoringFurtherWrites) {
|
|
return;
|
|
}
|
|
|
|
char[] buffer = CharBuffer;
|
|
|
|
if (count == 0) {
|
|
// Ensure flush if string is empty
|
|
}
|
|
else if (count < _charBufferFree) {
|
|
// fast path - 99% of string writes will not overrun the buffer
|
|
// avoid redundant arg checking in string.CopyTo
|
|
StringUtil.UnsafeStringCopy(s, index, buffer, _charBufferLength - _charBufferFree, count);
|
|
_charBufferFree -= count;
|
|
}
|
|
else {
|
|
int n;
|
|
|
|
while (count > 0) {
|
|
if (_charBufferFree == 0) {
|
|
FlushCharBuffer(false);
|
|
}
|
|
|
|
n = (count < _charBufferFree) ? count : _charBufferFree;
|
|
|
|
// avoid redundant arg checking in string.CopyTo
|
|
StringUtil.UnsafeStringCopy(s, index, buffer, _charBufferLength - _charBufferFree, n);
|
|
|
|
_charBufferFree -= n;
|
|
index += n;
|
|
count -= n;
|
|
}
|
|
}
|
|
|
|
if (!_responseBufferingOn) {
|
|
_response.Flush();
|
|
}
|
|
}
|
|
|
|
|
|
/// <devdoc>
|
|
/// <para>Sends an object to the client.</para>
|
|
/// </devdoc>
|
|
public override void Write(Object obj) {
|
|
if (_ignoringFurtherWrites) {
|
|
return;
|
|
}
|
|
|
|
if (obj != null)
|
|
Write(obj.ToString());
|
|
}
|
|
|
|
//
|
|
// Support for binary data
|
|
//
|
|
|
|
|
|
/// <devdoc>
|
|
/// <para>Sends a buffered stream of bytes to the client.</para>
|
|
/// </devdoc>
|
|
public void WriteBytes(byte[] buffer, int index, int count) {
|
|
if (_ignoringFurtherWrites) {
|
|
return;
|
|
}
|
|
|
|
WriteFromStream(buffer, index, count);
|
|
}
|
|
|
|
|
|
/// <devdoc>
|
|
/// <para>Writes out a CRLF pair into the the stream.</para>
|
|
/// </devdoc>
|
|
public override void WriteLine() {
|
|
if (_ignoringFurtherWrites) {
|
|
return;
|
|
}
|
|
|
|
// It turns out this is way more efficient than the TextWriter version of
|
|
// WriteLine which ends up calling Write with a 2 char array
|
|
|
|
char[] buffer = CharBuffer;
|
|
|
|
if (_charBufferFree < 2)
|
|
FlushCharBuffer(false);
|
|
|
|
int pos = _charBufferLength - _charBufferFree;
|
|
buffer[pos] = '\r';
|
|
buffer[pos + 1] = '\n';
|
|
_charBufferFree -= 2;
|
|
|
|
if (!_responseBufferingOn)
|
|
_response.Flush();
|
|
}
|
|
|
|
/*
|
|
* The Stream for writing binary data
|
|
*/
|
|
|
|
/// <devdoc>
|
|
/// <para> Enables binary output to the client.</para>
|
|
/// </devdoc>
|
|
public Stream OutputStream {
|
|
get { return _stream;}
|
|
}
|
|
|
|
}
|
|
}
|