File: HttpWriter.cs
Project: ndp\fx\src\xsp\system\Web\System.Web.csproj (System.Web)
//------------------------------------------------------------------------------
// <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;}
        }
 
    }
}