File: HttpBufferlessInputStream.cs
Project: ndp\fx\src\xsp\system\Web\System.Web.csproj (System.Web)
//------------------------------------------------------------------------------
// <copyright file="HttpBufferlessInputStream.cs" company="Microsoft">
//     Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>
//------------------------------------------------------------------------------
 
/*
 * Bufferless Input stream used in response and uploaded file objects
 *
 * Copyright (c) 2009 Microsoft Corporation
 */
 
namespace System.Web {
 
    using System.IO;
    using System.Security;
    using System.Security.Permissions;
    using System.Threading;
    using System.Web.Hosting;
    using System.Web.Configuration;
    using System.Web.Management;
    using System.Web.Util;
 
    internal class HttpBufferlessInputStream : Stream {
        private long                     _position;
        private long                     _length;
        private long                     _maxRequestLength;
        private bool                     _disableMaxRequestLength;
        private int                      _fileThreshold;
        private bool                     _preloadedContentRead;
        private HttpContext              _context;
        private int                      _preloadedBytesRead;
        private bool                     _persistEntityBody;
        private HttpRawUploadedContent   _rawContent;
        private byte[]                   _buffer;
        private int                      _offset;
        private int                      _count;
        private int                      _remainingBytes;
 
        internal HttpBufferlessInputStream(HttpContext context, bool persistEntityBody, bool disableMaxRequestLength) {
            _context = context;
            _persistEntityBody = persistEntityBody;
            _disableMaxRequestLength = disableMaxRequestLength;
 
            // Check max-request-length for preloaded content
            HttpRuntimeSection section = RuntimeConfig.GetConfig(_context).HttpRuntime;
            _maxRequestLength = section.MaxRequestLengthBytes;
            _fileThreshold = section.RequestLengthDiskThresholdBytes;
 
            if (_persistEntityBody) {
                _rawContent = new HttpRawUploadedContent(_fileThreshold, _context.Request.ContentLength);
            }
            
            int contentLength = _context.Request.ContentLength;
            _remainingBytes = (contentLength > 0) ? contentLength : Int32.MaxValue;
            _length = contentLength;
        }
 
        internal bool PersistEntityBody { 
            get { 
                return _persistEntityBody; 
            } 
        }
 
        /////////////////////////////////////////////////////////////////////////////
        /////////////////////////////////////////////////////////////////////////////
        /////////////////////////////////////////////////////////////////////////////
 
        // A call to Close is required for proper operation of a stream. Normally the
        // raw content will have already been set, but if it was not, we set it now,
        // even if the user of GetBufferedInputStream did not read the entire request
        // entity body.  Since HttpRequest.Dispose will dispose the raw content, this
        // helps to ensure that any temporary files are deleted.
        protected override void Dispose(bool disposing) {
            if (disposing && _persistEntityBody) {
                SetRawContentOnce();
            }
            base.Dispose(disposing);
        }
 
        public override bool CanRead {
            get {
                return true;
            }
        }
 
        public override bool CanSeek {
            get {
                return false;
            }
        }
 
        public override bool CanWrite {
            get {
                return false;
            }
        }
 
        public override long Length {
            get {
                return _length;
            }
        }
 
        public override long Position {
            get {
                return _position;
            }
            set {
                throw new NotSupportedException();
            }
        }
 
        public override void Flush() {
        }
 
        public override void SetLength(long length) {
            throw new NotSupportedException();
        }
        public override long Seek(long offset, SeekOrigin origin) {
            throw new NotSupportedException();
        }
 
        public override void Write(byte[] buffer, int offset, int count) {
            throw new NotSupportedException();
        }
 
        // Caller may invoke this repeatedly until EndRead returns zero, at which point the entire entity has been read.
        public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback callback, Object state) {
            HttpWorkerRequest wr = _context.WorkerRequest;
            // Only perform an async read if the worker request supports it and we're not in a cancellable period.
            // If we were to allow async read in a cancellable period, the timeout manager could raise a ThreadAbortEx and
            // corrupt the state of the request.
            if (wr != null && wr.SupportsAsyncRead && !_context.IsInCancellablePeriod) {
                if (!_preloadedContentRead) {
                    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"));
                    _preloadedBytesRead = GetPreloadedContent(buffer, ref offset, ref count);
                }
                if (_remainingBytes == 0) {
                    // set count to zero and invoke BeginRead to return an async result
                    count = 0;
                }
                if (_persistEntityBody) {
                    // hold a reference so we can add bytes to _rawContent when EndRead is called
                    _buffer = buffer;
                    _offset = offset;
                    _count = count;
                }
                try {
                    return wr.BeginRead(buffer, offset, count, callback, state);
                }
                catch(HttpException) {
                    if (_persistEntityBody) {
                        SetRawContentOnce();
                    }
                    throw;
                }
            }
            else {
                // perform a sync read
                return base.BeginRead(buffer, offset, count, callback, state);
            }
        }
        
        // When this returns zero, the entire entity has been read.
        public override int EndRead(IAsyncResult asyncResult) {
            HttpWorkerRequest wr = _context.WorkerRequest;
            if (wr != null && wr.SupportsAsyncRead && !_context.IsInCancellablePeriod) {
                int totalBytesRead = _preloadedBytesRead;
                if (_preloadedBytesRead > 0) {
                    _preloadedBytesRead = 0;
                }
                int bytesRead = 0;
                try {
                    bytesRead = wr.EndRead(asyncResult);
                }
                catch(HttpException) {
                    if (_persistEntityBody) {
                        SetRawContentOnce();
                    }
                    throw;
                }
                totalBytesRead += bytesRead;
                if (bytesRead > 0) {
                    if (_persistEntityBody) {
                        if (_rawContent != null) {
                            _rawContent.AddBytes(_buffer, _offset, bytesRead);
                        }
                        _buffer = null;
                        _offset = 0;
                        _count = 0;
                    }
                    int dummy1 = 0, dummy2 = 0, dummy3 = 0;
                    UpdateCounters(bytesRead, ref dummy1, ref dummy2, ref dummy3);
                }
                if (_persistEntityBody
                    // we might attempt a read with count == 0, in which case bytesRead will
                    // be zero but we may not be done reading the entity body. Don't set raw
                    // content until bytesRead is 0 and count is not 0 or _remainingBytes is 0
                    && ((bytesRead == 0 && _count != 0) || _remainingBytes == 0)) {
                    SetRawContentOnce();
                }
                return totalBytesRead;
            }
            else {
                return base.EndRead(asyncResult);
            }
        }
 
        // Caller may invoke this repeatedly until it returns zero, at which point the entire entity has been read.
        public override int Read(byte[] buffer, int offset, int count) {
            HttpWorkerRequest wr = _context.WorkerRequest;
            if (wr == null || count == 0)
                return 0;
            if (buffer == null)
                throw new ArgumentNullException("buffer");
            if (offset < 0 || offset + count > buffer.Length)
                throw new ArgumentException(null, "offset");
            if (count < 0)
                throw new ArgumentException(null, "count");
 
            int totalBytesRead = GetPreloadedContent(buffer, ref offset, ref count);
            int bytesRead = 0;
            // We are done if the count == 0 or there is no more content
            while (count > 0 && _remainingBytes != 0) {
                // Do the actual read
                bytesRead = wr.ReadEntityBody(buffer, offset, count);
                if (bytesRead <= 0) {
                    if (!_context.Response.IsClientConnected) {
                        if (_persistEntityBody) {
                            SetRawContentOnce();
                        }
                        throw new HttpException(SR.GetString(SR.HttpBufferlessInputStream_ClientDisconnected));
                    }
                    break;
                }
                if (_persistEntityBody) {
                    if (_rawContent != null) {
                        _rawContent.AddBytes(buffer, offset, bytesRead);
                    }
                }
                UpdateCounters(bytesRead, ref offset, ref count, ref totalBytesRead);
            }
            if (_persistEntityBody 
                // we might attempt a read with count == 0, in which case bytesRead will
                // be zero but we may not be done reading the entity body. Don't set raw
                // content until bytesRead is 0 and count is not 0 or _remainingBytes is 0
                && ((bytesRead == 0 && count != 0) || _remainingBytes == 0)) {
                SetRawContentOnce();
            }
            return totalBytesRead;
        }
 
        private int GetPreloadedContent(byte[] buffer, ref int offset, ref int count) {
            if (_preloadedContentRead) {
                return 0;
            }
 
            // validate once before reading preloaded bytes
            if (_position == 0) {
                ValidateRequestEntityLength();
            }
 
            int totalBytesRead = 0;
            int preloadedRemaining = 0;            
            byte [] preloadedContent = _context.WorkerRequest.GetPreloadedEntityBody();
            if (preloadedContent != null) {
                // Read preloaded content
                preloadedRemaining = preloadedContent.Length - (int) _position;
                int bytesRead = Math.Min(count, preloadedRemaining);
                Buffer.BlockCopy(preloadedContent, (int) _position, buffer, offset, bytesRead);
                if (_persistEntityBody) {
                    if (_rawContent != null) {
                        _rawContent.AddBytes(preloadedContent, (int) _position, bytesRead);
                    }
                }
                UpdateCounters(bytesRead, ref offset, ref count, ref totalBytesRead);
            }
            // are we done reading preloaded content
            if (totalBytesRead == preloadedRemaining) {
                _preloadedContentRead = true;
                if (_context.WorkerRequest.IsEntireEntityBodyIsPreloaded()) {
                    _remainingBytes = 0;
                }
            }
            return totalBytesRead;
        }
 
        /////////////////////////////////////////////////////////////////////////////
        /////////////////////////////////////////////////////////////////////////////
        // Helper function to increment variables in Read API
        private void UpdateCounters(int bytesRead, ref int offset, ref int count, ref int totalBytesRead) {
            _context.WorkerRequest.UpdateRequestCounters(bytesRead);
            count -= bytesRead;
            offset += bytesRead;
            _position += bytesRead;
            _remainingBytes -= bytesRead;
            totalBytesRead += bytesRead;
            if (_length < _position)
                _length = _position;
            ValidateRequestEntityLength();
        }
 
        private void ValidateRequestEntityLength() {
            if (!_disableMaxRequestLength && Length > _maxRequestLength) {
                if ( !(_context.WorkerRequest is IIS7WorkerRequest) ) {
                    _context.Response.CloseConnectionAfterError();
                }
                throw new HttpException(SR.GetString(SR.Max_request_length_exceeded), null, WebEventCodes.RuntimeErrorPostTooLarge);
            }
        }
 
        private void SetRawContentOnce() {
            if (_persistEntityBody && _rawContent != null) {
                _rawContent.DoneAddingBytes();
                _context.Request.SetRawContent(_rawContent);
                _rawContent = null;
            }
        }
    }
}