//------------------------------------------------------------------------------ // // Copyright (c) Microsoft Corporation. All rights reserved. // //------------------------------------------------------------------------------ /* * 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; } } } }