536cd135cc
Former-commit-id: 5624ac747d633e885131e8349322922b6a59baaa
329 lines
14 KiB
C#
329 lines
14 KiB
C#
//------------------------------------------------------------------------------
|
|
// <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;
|
|
}
|
|
}
|
|
}
|
|
}
|