1 //------------------------------------------------------------------------------
2 // <copyright file="HttpBufferlessInputStream.cs" company="Microsoft">
3 // Copyright (c) Microsoft Corporation. All rights reserved.
5 //------------------------------------------------------------------------------
8 * Bufferless Input stream used in response and uploaded file objects
10 * Copyright (c) 2009 Microsoft Corporation
13 namespace System
.Web
{
16 using System
.Security
;
17 using System
.Security
.Permissions
;
18 using System
.Threading
;
19 using System
.Web
.Hosting
;
20 using System
.Web
.Configuration
;
21 using System
.Web
.Management
;
22 using System
.Web
.Util
;
24 internal class HttpBufferlessInputStream
: Stream
{
25 private long _position
;
27 private long _maxRequestLength
;
28 private bool _disableMaxRequestLength
;
29 private int _fileThreshold
;
30 private bool _preloadedContentRead
;
31 private HttpContext _context
;
32 private int _preloadedBytesRead
;
33 private bool _persistEntityBody
;
34 private HttpRawUploadedContent _rawContent
;
35 private byte[] _buffer
;
38 private int _remainingBytes
;
40 internal HttpBufferlessInputStream(HttpContext context
, bool persistEntityBody
, bool disableMaxRequestLength
) {
42 _persistEntityBody
= persistEntityBody
;
43 _disableMaxRequestLength
= disableMaxRequestLength
;
45 // Check max-request-length for preloaded content
46 HttpRuntimeSection section
= RuntimeConfig
.GetConfig(_context
).HttpRuntime
;
47 _maxRequestLength
= section
.MaxRequestLengthBytes
;
48 _fileThreshold
= section
.RequestLengthDiskThresholdBytes
;
50 if (_persistEntityBody
) {
51 _rawContent
= new HttpRawUploadedContent(_fileThreshold
, _context
.Request
.ContentLength
);
54 int contentLength
= _context
.Request
.ContentLength
;
55 _remainingBytes
= (contentLength
> 0) ? contentLength
: Int32
.MaxValue
;
56 _length
= contentLength
;
59 internal bool PersistEntityBody
{
61 return _persistEntityBody
;
65 /////////////////////////////////////////////////////////////////////////////
66 /////////////////////////////////////////////////////////////////////////////
67 /////////////////////////////////////////////////////////////////////////////
69 // A call to Close is required for proper operation of a stream. Normally the
70 // raw content will have already been set, but if it was not, we set it now,
71 // even if the user of GetBufferedInputStream did not read the entire request
72 // entity body. Since HttpRequest.Dispose will dispose the raw content, this
73 // helps to ensure that any temporary files are deleted.
74 protected override void Dispose(bool disposing
) {
75 if (disposing
&& _persistEntityBody
) {
78 base.Dispose(disposing
);
81 public override bool CanRead
{
87 public override bool CanSeek
{
93 public override bool CanWrite
{
99 public override long Length
{
105 public override long Position
{
110 throw new NotSupportedException();
114 public override void Flush() {
117 public override void SetLength(long length
) {
118 throw new NotSupportedException();
120 public override long Seek(long offset
, SeekOrigin origin
) {
121 throw new NotSupportedException();
124 public override void Write(byte[] buffer
, int offset
, int count
) {
125 throw new NotSupportedException();
128 // Caller may invoke this repeatedly until EndRead returns zero, at which point the entire entity has been read.
129 public override IAsyncResult
BeginRead(byte[] buffer
, int offset
, int count
, AsyncCallback callback
, Object state
) {
130 HttpWorkerRequest wr
= _context
.WorkerRequest
;
131 // Only perform an async read if the worker request supports it and we're not in a cancellable period.
132 // If we were to allow async read in a cancellable period, the timeout manager could raise a ThreadAbortEx and
133 // corrupt the state of the request.
134 if (wr
!= null && wr
.SupportsAsyncRead
&& !_context
.IsInCancellablePeriod
) {
135 if (!_preloadedContentRead
) {
137 throw new ArgumentNullException("buffer");
139 throw new ArgumentOutOfRangeException("offset");
141 throw new ArgumentOutOfRangeException("count");
142 if (buffer
.Length
- offset
< count
)
143 throw new ArgumentException(SR
.GetString(SR
.InvalidOffsetOrCount
, "offset", "count"));
144 _preloadedBytesRead
= GetPreloadedContent(buffer
, ref offset
, ref count
);
146 if (_remainingBytes
== 0) {
147 // set count to zero and invoke BeginRead to return an async result
150 if (_persistEntityBody
) {
151 // hold a reference so we can add bytes to _rawContent when EndRead is called
157 return wr
.BeginRead(buffer
, offset
, count
, callback
, state
);
159 catch(HttpException
) {
160 if (_persistEntityBody
) {
167 // perform a sync read
168 return base.BeginRead(buffer
, offset
, count
, callback
, state
);
172 // When this returns zero, the entire entity has been read.
173 public override int EndRead(IAsyncResult asyncResult
) {
174 HttpWorkerRequest wr
= _context
.WorkerRequest
;
175 if (wr
!= null && wr
.SupportsAsyncRead
&& !_context
.IsInCancellablePeriod
) {
176 int totalBytesRead
= _preloadedBytesRead
;
177 if (_preloadedBytesRead
> 0) {
178 _preloadedBytesRead
= 0;
182 bytesRead
= wr
.EndRead(asyncResult
);
184 catch(HttpException
) {
185 if (_persistEntityBody
) {
190 totalBytesRead
+= bytesRead
;
192 if (_persistEntityBody
) {
193 if (_rawContent
!= null) {
194 _rawContent
.AddBytes(_buffer
, _offset
, bytesRead
);
200 int dummy1
= 0, dummy2
= 0, dummy3
= 0;
201 UpdateCounters(bytesRead
, ref dummy1
, ref dummy2
, ref dummy3
);
203 if (_persistEntityBody
204 // we might attempt a read with count == 0, in which case bytesRead will
205 // be zero but we may not be done reading the entity body. Don't set raw
206 // content until bytesRead is 0 and count is not 0 or _remainingBytes is 0
207 && ((bytesRead
== 0 && _count
!= 0) || _remainingBytes
== 0)) {
210 return totalBytesRead
;
213 return base.EndRead(asyncResult
);
217 // Caller may invoke this repeatedly until it returns zero, at which point the entire entity has been read.
218 public override int Read(byte[] buffer
, int offset
, int count
) {
219 HttpWorkerRequest wr
= _context
.WorkerRequest
;
220 if (wr
== null || count
== 0)
223 throw new ArgumentNullException("buffer");
224 if (offset
< 0 || offset
+ count
> buffer
.Length
)
225 throw new ArgumentException(null, "offset");
227 throw new ArgumentException(null, "count");
229 int totalBytesRead
= GetPreloadedContent(buffer
, ref offset
, ref count
);
231 // We are done if the count == 0 or there is no more content
232 while (count
> 0 && _remainingBytes
!= 0) {
233 // Do the actual read
234 bytesRead
= wr
.ReadEntityBody(buffer
, offset
, count
);
235 if (bytesRead
<= 0) {
236 if (!_context
.Response
.IsClientConnected
) {
237 if (_persistEntityBody
) {
240 throw new HttpException(SR
.GetString(SR
.HttpBufferlessInputStream_ClientDisconnected
));
244 if (_persistEntityBody
) {
245 if (_rawContent
!= null) {
246 _rawContent
.AddBytes(buffer
, offset
, bytesRead
);
249 UpdateCounters(bytesRead
, ref offset
, ref count
, ref totalBytesRead
);
251 if (_persistEntityBody
252 // we might attempt a read with count == 0, in which case bytesRead will
253 // be zero but we may not be done reading the entity body. Don't set raw
254 // content until bytesRead is 0 and count is not 0 or _remainingBytes is 0
255 && ((bytesRead
== 0 && count
!= 0) || _remainingBytes
== 0)) {
258 return totalBytesRead
;
261 private int GetPreloadedContent(byte[] buffer
, ref int offset
, ref int count
) {
262 if (_preloadedContentRead
) {
266 // validate once before reading preloaded bytes
267 if (_position
== 0) {
268 ValidateRequestEntityLength();
271 int totalBytesRead
= 0;
272 int preloadedRemaining
= 0;
273 byte [] preloadedContent
= _context
.WorkerRequest
.GetPreloadedEntityBody();
274 if (preloadedContent
!= null) {
275 // Read preloaded content
276 preloadedRemaining
= preloadedContent
.Length
- (int) _position
;
277 int bytesRead
= Math
.Min(count
, preloadedRemaining
);
278 Buffer
.BlockCopy(preloadedContent
, (int) _position
, buffer
, offset
, bytesRead
);
279 if (_persistEntityBody
) {
280 if (_rawContent
!= null) {
281 _rawContent
.AddBytes(preloadedContent
, (int) _position
, bytesRead
);
284 UpdateCounters(bytesRead
, ref offset
, ref count
, ref totalBytesRead
);
286 // are we done reading preloaded content
287 if (totalBytesRead
== preloadedRemaining
) {
288 _preloadedContentRead
= true;
289 if (_context
.WorkerRequest
.IsEntireEntityBodyIsPreloaded()) {
293 return totalBytesRead
;
296 /////////////////////////////////////////////////////////////////////////////
297 /////////////////////////////////////////////////////////////////////////////
298 // Helper function to increment variables in Read API
299 private void UpdateCounters(int bytesRead
, ref int offset
, ref int count
, ref int totalBytesRead
) {
300 _context
.WorkerRequest
.UpdateRequestCounters(bytesRead
);
303 _position
+= bytesRead
;
304 _remainingBytes
-= bytesRead
;
305 totalBytesRead
+= bytesRead
;
306 if (_length
< _position
)
308 ValidateRequestEntityLength();
311 private void ValidateRequestEntityLength() {
312 if (!_disableMaxRequestLength
&& Length
> _maxRequestLength
) {
313 if ( !(_context
.WorkerRequest
is IIS7WorkerRequest
) ) {
314 _context
.Response
.CloseConnectionAfterError();
316 throw new HttpException(SR
.GetString(SR
.Max_request_length_exceeded
), null, WebEventCodes
.RuntimeErrorPostTooLarge
);
320 private void SetRawContentOnce() {
321 if (_persistEntityBody
&& _rawContent
!= null) {
322 _rawContent
.DoneAddingBytes();
323 _context
.Request
.SetRawContent(_rawContent
);