1 // ------------------------------------------------------------------------------
2 // <copyright file="_HttpResponseStream.cs" company="Microsoft">
3 //     Copyright (c) Microsoft Corporation.  All rights reserved.
4 // </copyright>
5 // ------------------------------------------------------------------------------
6 
7 namespace System.Net {
8     using System.IO;
9     using System.Runtime.InteropServices;
10     using System.Threading;
11     using System.Globalization;
12     using System.Security.Permissions;
13     using System.Threading.Tasks;
14     using System.Diagnostics.CodeAnalysis;
15 
16     unsafe class HttpResponseStream : Stream {
17         private HttpListenerContext m_HttpContext;
18         private long m_LeftToWrite = long.MinValue;
19         private bool m_Closed;
20         private bool m_InOpaqueMode;
21         // The last write needs special handling to cancel.
22         private HttpResponseStreamAsyncResult m_LastWrite;
23 
HttpResponseStream(HttpListenerContext httpContext)24         internal HttpResponseStream(HttpListenerContext httpContext) {
25             GlobalLog.Print("HttpResponseStream#" + ValidationHelper.HashString(this) + "::.ctor() HttpListenerContext##" + ValidationHelper.HashString(httpContext));
26             m_HttpContext = httpContext;
27         }
28 
ComputeLeftToWrite()29         internal UnsafeNclNativeMethods.HttpApi.HTTP_FLAGS ComputeLeftToWrite() {
30             GlobalLog.Print("HttpResponseStream#" + ValidationHelper.HashString(this) + "::ComputeLeftToWrite() on entry m_LeftToWrite:" + m_LeftToWrite);
31             UnsafeNclNativeMethods.HttpApi.HTTP_FLAGS flags = UnsafeNclNativeMethods.HttpApi.HTTP_FLAGS.NONE;
32             if (!m_HttpContext.Response.ComputedHeaders) {
33                 flags = m_HttpContext.Response.ComputeHeaders();
34             }
35             if (m_LeftToWrite==long.MinValue) {
36                 UnsafeNclNativeMethods.HttpApi.HTTP_VERB method = m_HttpContext.GetKnownMethod();
37                 m_LeftToWrite = method != UnsafeNclNativeMethods.HttpApi.HTTP_VERB.HttpVerbHEAD ? m_HttpContext.Response.ContentLength64 : 0;
38                 GlobalLog.Print("HttpResponseStream#" + ValidationHelper.HashString(this) + "::ComputeLeftToWrite() computed m_LeftToWrite:" + m_LeftToWrite);
39             }
40             return flags;
41         }
42 
43         public override bool CanSeek {
44             get {
45                 return false;
46             }
47         }
48 
49         public override bool CanWrite {
50             get {
51                 return true;
52             }
53         }
54 
55         public override bool CanRead {
56             get {
57                 return false;
58             }
59         }
60 
61         internal bool Closed
62         {
63             get
64             {
65                 return m_Closed;
66             }
67         }
68 
69         internal HttpListenerContext InternalHttpContext
70         {
71             get
72             {
73                 return m_HttpContext;
74             }
75         }
76 
SetClosedFlag()77         internal void SetClosedFlag()
78         {
79             m_Closed = true;
80         }
81 
Flush()82         public override void Flush() {
83         }
84 
FlushAsync(CancellationToken cancellationToken)85         public override Task FlushAsync(CancellationToken cancellationToken)
86         {
87             return Task.CompletedTask;
88         }
89 
90         public override long Length {
91             get {
92                 throw new NotSupportedException(SR.GetString(SR.net_noseek));
93             }
94 
95         }
96 
97         public override long Position {
98             get {
99                 throw new NotSupportedException(SR.GetString(SR.net_noseek));
100             }
101             set {
102                 throw new NotSupportedException(SR.GetString(SR.net_noseek));
103             }
104         }
105 
Seek(long offset, SeekOrigin origin)106         public override long Seek(long offset, SeekOrigin origin) {
107             throw new NotSupportedException(SR.GetString(SR.net_noseek));
108         }
109 
SetLength(long value)110         public override void SetLength(long value) {
111             throw new NotSupportedException(SR.GetString(SR.net_noseek));
112         }
113 
Read([In, Out] byte[] buffer, int offset, int size)114         public override int Read([In, Out] byte[] buffer, int offset, int size) {
115             throw new InvalidOperationException(SR.GetString(SR.net_writeonlystream));
116         }
117 
118         [HostProtection(ExternalThreading=true)]
BeginRead(byte[] buffer, int offset, int size, AsyncCallback callback, object state)119         public override IAsyncResult BeginRead(byte[] buffer, int offset, int size, AsyncCallback callback, object state) {
120             throw new InvalidOperationException(SR.GetString(SR.net_writeonlystream));
121         }
122 
EndRead(IAsyncResult asyncResult)123         public override int EndRead(IAsyncResult asyncResult) {
124             throw new InvalidOperationException(SR.GetString(SR.net_writeonlystream));
125         }
126 
Write(byte[] buffer, int offset, int size)127         public override void Write(byte[] buffer, int offset, int size) {
128             if(Logging.On)Logging.Enter(Logging.HttpListener, this, "Write", "");
129             GlobalLog.Print("HttpResponseStream#" + ValidationHelper.HashString(this) + "::Write() buffer.Length:" + buffer.Length + " size:" + size + " offset:" + offset);
130             if (buffer==null) {
131                 throw new ArgumentNullException("buffer");
132             }
133             if (offset<0 || offset>buffer.Length) {
134                 throw new ArgumentOutOfRangeException("offset");
135             }
136             if (size<0 || size>buffer.Length-offset) {
137                 throw new ArgumentOutOfRangeException("size");
138             }
139             UnsafeNclNativeMethods.HttpApi.HTTP_FLAGS flags = ComputeLeftToWrite();
140             if (m_Closed || (size == 0 && m_LeftToWrite != 0)) {
141                 if(Logging.On)Logging.Exit(Logging.HttpListener, this, "Write", "");
142                 return;
143             }
144             if (m_LeftToWrite>=0 && size>m_LeftToWrite) {
145                 throw new ProtocolViolationException(SR.GetString(SR.net_entitytoobig));
146             }
147 
148             uint statusCode;
149             uint dataToWrite = (uint)size;
150             SafeLocalFree bufferAsIntPtr = null;
151             IntPtr pBufferAsIntPtr = IntPtr.Zero;
152             bool sentHeaders = m_HttpContext.Response.SentHeaders;
153             try {
154                 if (size == 0)
155                 {
156                     statusCode = m_HttpContext.Response.SendHeaders(null, null, flags, false);
157                 }
158                 else
159                 {
160                     fixed (byte* pDataBuffer = buffer) {
161                         byte* pBuffer = pDataBuffer;
162                         if (m_HttpContext.Response.BoundaryType == BoundaryType.Chunked) {
163                             //
164 
165 
166                             string chunkHeader = size.ToString("x", CultureInfo.InvariantCulture);
167                             dataToWrite = dataToWrite + (uint)(chunkHeader.Length + 4);
168                             bufferAsIntPtr = SafeLocalFree.LocalAlloc((int)dataToWrite);
169                             pBufferAsIntPtr = bufferAsIntPtr.DangerousGetHandle();
170                             for (int i = 0; i < chunkHeader.Length; i++) {
171                                 Marshal.WriteByte(pBufferAsIntPtr, i, (byte)chunkHeader[i]);
172                             }
173                             Marshal.WriteInt16(pBufferAsIntPtr, chunkHeader.Length, 0x0A0D);
174                             Marshal.Copy(buffer, offset, IntPtrHelper.Add(pBufferAsIntPtr, chunkHeader.Length + 2), size);
175                             Marshal.WriteInt16(pBufferAsIntPtr, (int)(dataToWrite - 2), 0x0A0D);
176                             pBuffer = (byte*)pBufferAsIntPtr;
177                             offset = 0;
178                         }
179                         UnsafeNclNativeMethods.HttpApi.HTTP_DATA_CHUNK dataChunk = new UnsafeNclNativeMethods.HttpApi.HTTP_DATA_CHUNK();
180                         dataChunk.DataChunkType = UnsafeNclNativeMethods.HttpApi.HTTP_DATA_CHUNK_TYPE.HttpDataChunkFromMemory;
181                         dataChunk.pBuffer = (byte*)(pBuffer + offset);
182                         dataChunk.BufferLength = dataToWrite;
183 
184                         flags |= m_LeftToWrite == size ? UnsafeNclNativeMethods.HttpApi.HTTP_FLAGS.NONE : UnsafeNclNativeMethods.HttpApi.HTTP_FLAGS.HTTP_SEND_RESPONSE_FLAG_MORE_DATA;
185                         if (!sentHeaders) {
186                             statusCode = m_HttpContext.Response.SendHeaders(&dataChunk, null, flags, false);
187                         }
188                         else {
189                             GlobalLog.Print("HttpResponseStream#" + ValidationHelper.HashString(this) + "::Write() calling UnsafeNclNativeMethods.HttpApi.HttpSendResponseEntityBody");
190 
191                             statusCode =
192                                 UnsafeNclNativeMethods.HttpApi.HttpSendResponseEntityBody(
193                                     m_HttpContext.RequestQueueHandle,
194                                     m_HttpContext.RequestId,
195                                     (uint)flags,
196                                     1,
197                                     &dataChunk,
198                                     null,
199                                     SafeLocalFree.Zero,
200                                     0,
201                                     null,
202                                     null);
203 
204                             GlobalLog.Print("HttpResponseStream#" + ValidationHelper.HashString(this) + "::Write() call to UnsafeNclNativeMethods.HttpApi.HttpSendResponseEntityBody returned:" + statusCode);
205                             if (m_HttpContext.Listener.IgnoreWriteExceptions) {
206                                 GlobalLog.Print("HttpResponseStream#" + ValidationHelper.HashString(this) + "::Write() suppressing error");
207                                 statusCode = UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS;
208                             }
209                         }
210                     }
211                 }
212             }
213             finally {
214                 if (bufferAsIntPtr != null) {
215                     // free unmanaged buffer
216                     bufferAsIntPtr.Close();
217                 }
218             }
219 
220             if (statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS && statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_HANDLE_EOF) {
221                 Exception exception = new HttpListenerException((int)statusCode);
222                 if(Logging.On)Logging.Exception(Logging.HttpListener, this, "Write", exception);
223                 m_Closed = true;
224                 m_HttpContext.Abort();
225                 throw exception;
226             }
227             UpdateAfterWrite(dataToWrite);
228             if(Logging.On)Logging.Dump(Logging.HttpListener, this, "Write", buffer, offset, (int)dataToWrite);
229             if(Logging.On)Logging.Exit(Logging.HttpListener, this, "Write", "");
230         }
231 
232 
233         [HostProtection(ExternalThreading=true)]
BeginWrite(byte[] buffer, int offset, int size, AsyncCallback callback, object state)234         public override IAsyncResult BeginWrite(byte[] buffer, int offset, int size, AsyncCallback callback, object state) {
235            GlobalLog.Print("HttpResponseStream#" + ValidationHelper.HashString(this) + "::BeginWrite() buffer.Length:" + buffer.Length + " size:" + size + " offset:" + offset);
236             if (buffer==null) {
237                 throw new ArgumentNullException("buffer");
238             }
239             if (offset<0 || offset>buffer.Length) {
240                 throw new ArgumentOutOfRangeException("offset");
241             }
242             if (size<0 || size>buffer.Length-offset) {
243                 throw new ArgumentOutOfRangeException("size");
244             }
245             UnsafeNclNativeMethods.HttpApi.HTTP_FLAGS flags = ComputeLeftToWrite();
246             if (m_Closed || (size == 0 && m_LeftToWrite != 0)) {
247                 if(Logging.On)Logging.Exit(Logging.HttpListener, this, "BeginWrite", "");
248                 HttpResponseStreamAsyncResult result = new HttpResponseStreamAsyncResult(this, state, callback);
249                 result.InvokeCallback((uint) 0);
250                 return result;
251             }
252             if (m_LeftToWrite>=0 && size>m_LeftToWrite) {
253                 throw new ProtocolViolationException(SR.GetString(SR.net_entitytoobig));
254             }
255 
256             uint statusCode;
257             uint bytesSent = 0;
258             flags |= m_LeftToWrite==size ? UnsafeNclNativeMethods.HttpApi.HTTP_FLAGS.NONE : UnsafeNclNativeMethods.HttpApi.HTTP_FLAGS.HTTP_SEND_RESPONSE_FLAG_MORE_DATA;
259             bool sentHeaders = m_HttpContext.Response.SentHeaders;
260             HttpResponseStreamAsyncResult asyncResult = new HttpResponseStreamAsyncResult(this, state, callback, buffer, offset, size, m_HttpContext.Response.BoundaryType==BoundaryType.Chunked, sentHeaders);
261 
262             // Update m_LeftToWrite now so we can queue up additional BeginWrite's without waiting for EndWrite.
263             UpdateAfterWrite((uint)((m_HttpContext.Response.BoundaryType == BoundaryType.Chunked) ? 0 : size));
264 
265             try {
266                 if (!sentHeaders) {
267                     statusCode = m_HttpContext.Response.SendHeaders(null, asyncResult, flags, false);
268                 }
269                 else {
270                     GlobalLog.Print("HttpResponseStream#" + ValidationHelper.HashString(this) + "::BeginWrite() calling UnsafeNclNativeMethods.HttpApi.HttpSendResponseEntityBody");
271 
272                     m_HttpContext.EnsureBoundHandle();
273                     statusCode =
274                         UnsafeNclNativeMethods.HttpApi.HttpSendResponseEntityBody(
275                             m_HttpContext.RequestQueueHandle,
276                             m_HttpContext.RequestId,
277                             (uint)flags,
278                             asyncResult.dataChunkCount,
279                             asyncResult.pDataChunks,
280                             &bytesSent,
281                             SafeLocalFree.Zero,
282                             0,
283                             asyncResult.m_pOverlapped,
284                             null);
285 
286                     GlobalLog.Print("HttpResponseStream#" + ValidationHelper.HashString(this) + "::BeginWrite() call to UnsafeNclNativeMethods.HttpApi.HttpSendResponseEntityBody returned:" + statusCode);
287                 }
288             }
289             catch (Exception e) {
290                 if (Logging.On) Logging.Exception(Logging.HttpListener, this, "BeginWrite", e);
291                 asyncResult.InternalCleanup();
292                 m_Closed = true;
293                 m_HttpContext.Abort();
294                 throw;
295             }
296 
297             if (statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS && statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_IO_PENDING) {
298                 asyncResult.InternalCleanup();
299                 if (m_HttpContext.Listener.IgnoreWriteExceptions && sentHeaders) {
300                     GlobalLog.Print("HttpResponseStream#" + ValidationHelper.HashString(this) + "::BeginWrite() suppressing error");
301                 }
302                 else {
303                     Exception exception = new HttpListenerException((int)statusCode);
304                     if (Logging.On) Logging.Exception(Logging.HttpListener, this, "BeginWrite", exception);
305                     m_Closed = true;
306                     m_HttpContext.Abort();
307                     throw exception;
308                 }
309             }
310 
311             if (statusCode == UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS && HttpListener.SkipIOCPCallbackOnSuccess)
312             {
313                 // IO operation completed synchronously - callback won't be called to signal completion.
314                 asyncResult.IOCompleted(statusCode, bytesSent);
315             }
316 
317             // Last write, cache it for special cancelation handling.
318             if ((flags & UnsafeNclNativeMethods.HttpApi.HTTP_FLAGS.HTTP_SEND_RESPONSE_FLAG_MORE_DATA) == 0)
319             {
320                 m_LastWrite = asyncResult;
321             }
322 
323             if(Logging.On)Logging.Exit(Logging.HttpListener, this, "BeginWrite", "");
324             return asyncResult;
325         }
326 
EndWrite(IAsyncResult asyncResult)327         public override void EndWrite(IAsyncResult asyncResult) {
328             if(Logging.On)Logging.Enter(Logging.HttpListener, this, "EndWrite", "");
329             GlobalLog.Print("HttpResponseStream#" + ValidationHelper.HashString(this) + "::EndWrite() asyncResult#" + ValidationHelper.HashString(asyncResult));
330             if (asyncResult==null) {
331                 throw new ArgumentNullException("asyncResult");
332             }
333             HttpResponseStreamAsyncResult castedAsyncResult = asyncResult as HttpResponseStreamAsyncResult;
334             if (castedAsyncResult==null || castedAsyncResult.AsyncObject!=this) {
335                 throw new ArgumentException(SR.GetString(SR.net_io_invalidasyncresult), "asyncResult");
336             }
337             if (castedAsyncResult.EndCalled) {
338                 throw new InvalidOperationException(SR.GetString(SR.net_io_invalidendcall, "EndWrite"));
339             }
340             castedAsyncResult.EndCalled = true;
341             // wait & then check for errors
342             object returnValue = castedAsyncResult.InternalWaitForCompletion();
343 
344             Exception exception = returnValue as Exception;
345             if (exception!=null) {
346                 GlobalLog.Print("HttpResponseStream#" + ValidationHelper.HashString(this) + "::EndWrite() rethrowing exception:" + exception);
347                 if(Logging.On)Logging.Exception(Logging.HttpListener, this, "EndWrite", exception);
348                 m_Closed = true;
349                 m_HttpContext.Abort();
350                 throw exception;
351             }
352             //
353 
354 
355             GlobalLog.Print("HttpResponseStream#" + ValidationHelper.HashString(this) + "::EndWrite()");
356             if(Logging.On)Logging.Exit(Logging.HttpListener, this, "EndWrite", "");
357         }
358 
UpdateAfterWrite(uint dataWritten)359         void UpdateAfterWrite(uint dataWritten) {
360             GlobalLog.Print("HttpResponseStream#" + ValidationHelper.HashString(this) + "::UpdateAfterWrite() dataWritten:" + dataWritten + " m_LeftToWrite:" + m_LeftToWrite + " m_Closed:" + m_Closed);
361             if (!m_InOpaqueMode)
362             {
363                 if (m_LeftToWrite>0) {
364                     // keep track of the data transferred
365                     m_LeftToWrite -= dataWritten;
366                 }
367                 if (m_LeftToWrite==0) {
368                     // in this case we already passed 0 as the flag, so we don't need to call HttpSendResponseEntityBody() when we Close()
369                     m_Closed = true;
370                 }
371             }
372             GlobalLog.Print("HttpResponseStream#" + ValidationHelper.HashString(this) + "::UpdateAfterWrite() dataWritten:" + dataWritten + " m_LeftToWrite:" + m_LeftToWrite + " m_Closed:" + m_Closed);
373         }
374 
Dispose(bool disposing)375         protected override void Dispose(bool disposing) {
376             if(Logging.On)Logging.Enter(Logging.HttpListener, this, "Close", "");
377 
378             try {
379                 if(disposing){
380                     GlobalLog.Print("HttpResponseStream#" + ValidationHelper.HashString(this) + "::Close() m_Closed:" + m_Closed);
381                     if (m_Closed) {
382                         if(Logging.On)Logging.Exit(Logging.HttpListener, this, "Close", "");
383                         return;
384                     }
385                     m_Closed = true;
386                     UnsafeNclNativeMethods.HttpApi.HTTP_FLAGS flags = ComputeLeftToWrite();
387                     if (m_LeftToWrite>0  && !m_InOpaqueMode) {
388                         throw new InvalidOperationException(SR.GetString(SR.net_io_notenoughbyteswritten));
389                     }
390                     bool sentHeaders = m_HttpContext.Response.SentHeaders;
391                     if (sentHeaders && m_LeftToWrite==0) {
392                         if(Logging.On)Logging.Exit(Logging.HttpListener, this, "Close", "");
393                         return;
394                     }
395 
396                     uint statusCode = 0;
397                     if ((m_HttpContext.Response.BoundaryType==BoundaryType.Chunked || m_HttpContext.Response.BoundaryType==BoundaryType.None) && (String.Compare(m_HttpContext.Request.HttpMethod, "HEAD", StringComparison.OrdinalIgnoreCase)!=0)) {
398                         if (m_HttpContext.Response.BoundaryType==BoundaryType.None) {
399                             flags |= UnsafeNclNativeMethods.HttpApi.HTTP_FLAGS.HTTP_SEND_RESPONSE_FLAG_DISCONNECT;
400                         }
401                         fixed (void* pBuffer = NclConstants.ChunkTerminator)
402                         {
403                             UnsafeNclNativeMethods.HttpApi.HTTP_DATA_CHUNK* pDataChunk = null;
404                             if (m_HttpContext.Response.BoundaryType==BoundaryType.Chunked) {
405                                 UnsafeNclNativeMethods.HttpApi.HTTP_DATA_CHUNK dataChunk = new UnsafeNclNativeMethods.HttpApi.HTTP_DATA_CHUNK();
406                                 dataChunk.DataChunkType = UnsafeNclNativeMethods.HttpApi.HTTP_DATA_CHUNK_TYPE.HttpDataChunkFromMemory;
407                                 dataChunk.pBuffer = (byte *)pBuffer;
408                                 dataChunk.BufferLength = (uint) NclConstants.ChunkTerminator.Length;
409                                 pDataChunk = &dataChunk;
410                             }
411                             if (!sentHeaders) {
412                                 statusCode = m_HttpContext.Response.SendHeaders(pDataChunk, null, flags, false);
413                             }
414                             else {
415                                 GlobalLog.Print("HttpResponseStream#" + ValidationHelper.HashString(this) + "::Close() calling UnsafeNclNativeMethods.HttpApi.HttpSendResponseEntityBody");
416 
417                                 statusCode =
418                                     UnsafeNclNativeMethods.HttpApi.HttpSendResponseEntityBody(
419                                         m_HttpContext.RequestQueueHandle,
420                                         m_HttpContext.RequestId,
421                                         (uint)flags,
422                                         pDataChunk!=null ? (ushort)1 : (ushort)0,
423                                         pDataChunk,
424                                         null,
425                                         SafeLocalFree.Zero,
426                                         0,
427                                         null,
428                                         null);
429 
430                                 GlobalLog.Print("HttpResponseStream#" + ValidationHelper.HashString(this) + "::Close() call to UnsafeNclNativeMethods.HttpApi.HttpSendResponseEntityBody returned:" + statusCode);
431                                 if (m_HttpContext.Listener.IgnoreWriteExceptions) {
432                                     GlobalLog.Print("HttpResponseStream#" + ValidationHelper.HashString(this) + "::Close() suppressing error");
433                                     statusCode = UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS;
434                                 }
435                             }
436                         }
437                     }
438                     else {
439                         if (!sentHeaders) {
440                             statusCode = m_HttpContext.Response.SendHeaders(null, null, flags, false);
441                         }
442                     }
443                     if (statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS && statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_HANDLE_EOF) {
444                         Exception exception = new HttpListenerException((int)statusCode);
445                         if(Logging.On)Logging.Exception(Logging.HttpListener, this, "Close", exception);
446                         m_HttpContext.Abort();
447                         throw exception;
448                     }
449                     m_LeftToWrite = 0;
450                 }
451             }
452             finally {
453                 base.Dispose(disposing);
454             }
455             if(Logging.On)Logging.Exit(Logging.HttpListener, this, "Dispose", "");
456         }
457 
SwitchToOpaqueMode()458         internal void SwitchToOpaqueMode()
459         {
460             GlobalLog.Print("HttpResponseStream#" + ValidationHelper.HashString(this) + "::SwitchToOpaqueMode()");
461             m_InOpaqueMode = true;
462             m_LeftToWrite = long.MaxValue;
463         }
464 
465         // The final Content-Length async write can only be cancelled by CancelIoEx.
466         // Sync can only be cancelled by CancelSynchronousIo, but we don't attempt this right now.
467         [SuppressMessage("Microsoft.Usage", "CA1806:DoNotIgnoreMethodResults", Justification =
468             "It is safe to ignore the return value on a cancel operation because the connection is being closed")]
CancelLastWrite(CriticalHandle requestQueueHandle)469         internal void CancelLastWrite(CriticalHandle requestQueueHandle)
470         {
471             HttpResponseStreamAsyncResult asyncState = m_LastWrite;
472             if (asyncState != null && !asyncState.IsCompleted)
473             {
474                 UnsafeNclNativeMethods.CancelIoEx(requestQueueHandle, asyncState.m_pOverlapped);
475             }
476         }
477     }
478 
479     unsafe class HttpResponseStreamAsyncResult : LazyAsyncResult {
480         internal NativeOverlapped* m_pOverlapped;
481         private UnsafeNclNativeMethods.HttpApi.HTTP_DATA_CHUNK[] m_DataChunks;
482         internal bool m_SentHeaders;
483 
484         private static readonly IOCompletionCallback s_IOCallback = new IOCompletionCallback(Callback);
485 
486         internal ushort dataChunkCount {
487             get {
488                 if (m_DataChunks == null) {
489                     return 0;
490                 }
491                 else {
492                     return (ushort)m_DataChunks.Length;
493                 }
494             }
495         }
496 
497         internal UnsafeNclNativeMethods.HttpApi.HTTP_DATA_CHUNK* pDataChunks {
498             get {
499                 if (m_DataChunks == null) {
500                     return null;
501                 }
502                 else  {
503                     return (UnsafeNclNativeMethods.HttpApi.HTTP_DATA_CHUNK*)(Marshal.UnsafeAddrOfPinnedArrayElement(m_DataChunks, 0));
504                 }
505             }
506         }
507 
HttpResponseStreamAsyncResult(object asyncObject, object userState, AsyncCallback callback)508         internal HttpResponseStreamAsyncResult(object asyncObject, object userState, AsyncCallback callback) : base(asyncObject, userState, callback) {
509         }
510 
511 
HttpResponseStreamAsyncResult(object asyncObject, object userState, AsyncCallback callback, byte[] buffer, int offset, int size, bool chunked, bool sentHeaders)512         internal HttpResponseStreamAsyncResult(object asyncObject, object userState, AsyncCallback callback, byte[] buffer, int offset, int size, bool chunked, bool sentHeaders): base(asyncObject, userState, callback){
513             m_SentHeaders = sentHeaders;
514             Overlapped overlapped = new Overlapped();
515             overlapped.AsyncResult = this;
516 
517             if (size == 0) {
518                 m_DataChunks = null;
519                 m_pOverlapped = overlapped.Pack(s_IOCallback, null);
520             }
521             else {
522                 m_DataChunks = new UnsafeNclNativeMethods.HttpApi.HTTP_DATA_CHUNK[chunked ? 3 : 1];
523 
524                 GlobalLog.Print("HttpResponseStreamAsyncResult#" + ValidationHelper.HashString(this) + "::.ctor() m_pOverlapped:0x" + ((IntPtr)m_pOverlapped).ToString("x8"));
525 
526                 object[] objectsToPin = new object[1 + m_DataChunks.Length];
527                 objectsToPin[m_DataChunks.Length] = m_DataChunks;
528 
529 
530                 int chunkHeaderOffset = 0;
531                 byte[] chunkHeaderBuffer = null;
532                 if (chunked) {
533                     chunkHeaderBuffer = ConnectStream.GetChunkHeader(size, out chunkHeaderOffset);
534 
535                     m_DataChunks[0] = new UnsafeNclNativeMethods.HttpApi.HTTP_DATA_CHUNK();
536                     m_DataChunks[0].DataChunkType = UnsafeNclNativeMethods.HttpApi.HTTP_DATA_CHUNK_TYPE.HttpDataChunkFromMemory;
537                     m_DataChunks[0].BufferLength = (uint)(chunkHeaderBuffer.Length - chunkHeaderOffset);
538 
539                     objectsToPin[0] = chunkHeaderBuffer;
540 
541                     m_DataChunks[1] = new UnsafeNclNativeMethods.HttpApi.HTTP_DATA_CHUNK();
542                     m_DataChunks[1].DataChunkType = UnsafeNclNativeMethods.HttpApi.HTTP_DATA_CHUNK_TYPE.HttpDataChunkFromMemory;
543                     m_DataChunks[1].BufferLength = (uint)size;
544 
545                     objectsToPin[1] = buffer;
546 
547                     m_DataChunks[2] = new UnsafeNclNativeMethods.HttpApi.HTTP_DATA_CHUNK();
548                     m_DataChunks[2].DataChunkType = UnsafeNclNativeMethods.HttpApi.HTTP_DATA_CHUNK_TYPE.HttpDataChunkFromMemory;
549                     m_DataChunks[2].BufferLength = (uint)NclConstants.CRLF.Length;
550 
551                     objectsToPin[2] = NclConstants.CRLF;
552 
553                 }
554                 else {
555                     m_DataChunks[0] = new UnsafeNclNativeMethods.HttpApi.HTTP_DATA_CHUNK();
556                     m_DataChunks[0].DataChunkType = UnsafeNclNativeMethods.HttpApi.HTTP_DATA_CHUNK_TYPE.HttpDataChunkFromMemory;
557                     m_DataChunks[0].BufferLength = (uint)size;
558 
559                     objectsToPin[0] = buffer;
560                 }
561 
562                 // This call will pin needed memory
563                 m_pOverlapped = overlapped.Pack(s_IOCallback, objectsToPin);
564 
565                 if (chunked)
566                 {
567                     m_DataChunks[0].pBuffer = (byte*)(Marshal.UnsafeAddrOfPinnedArrayElement(chunkHeaderBuffer, chunkHeaderOffset));
568                     m_DataChunks[1].pBuffer = (byte*)(Marshal.UnsafeAddrOfPinnedArrayElement(buffer, offset));
569                     m_DataChunks[2].pBuffer = (byte*)(Marshal.UnsafeAddrOfPinnedArrayElement(NclConstants.CRLF, 0));
570                 }
571                 else
572                 {
573                     m_DataChunks[0].pBuffer = (byte*)(Marshal.UnsafeAddrOfPinnedArrayElement(buffer, offset));
574                 }
575 
576             }
577         }
578 
IOCompleted(uint errorCode, uint numBytes)579         internal void IOCompleted(uint errorCode, uint numBytes)
580         {
581             IOCompleted(this, errorCode, numBytes);
582         }
583 
IOCompleted(HttpResponseStreamAsyncResult asyncResult, uint errorCode, uint numBytes)584         private static void IOCompleted(HttpResponseStreamAsyncResult asyncResult, uint errorCode, uint numBytes)
585         {
586             GlobalLog.Print("HttpResponseStreamAsyncResult#" + ValidationHelper.HashString(asyncResult) + "::Callback() errorCode:0x" + errorCode.ToString("x8") + " numBytes:" + numBytes);
587             object result = null;
588             try {
589                 if (errorCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS && errorCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_HANDLE_EOF) {
590                     asyncResult.ErrorCode = (int)errorCode;
591                     result = new HttpListenerException((int)errorCode);
592                 }
593                 else {
594                     // if we sent headers and body together, numBytes will be the total, but we need to only account for the data
595                     // result = numBytes;
596                     if (asyncResult.m_DataChunks == null) {
597                         result = (uint) 0;
598                         if (Logging.On) { Logging.Dump(Logging.HttpListener, asyncResult, "Callback", IntPtr.Zero, 0); }
599                     }
600                     else {
601                         result = asyncResult.m_DataChunks.Length == 1 ? asyncResult.m_DataChunks[0].BufferLength : 0;
602                         if (Logging.On) { for (int i = 0; i < asyncResult.m_DataChunks.Length; i++) { Logging.Dump(Logging.HttpListener, asyncResult, "Callback", (IntPtr)asyncResult.m_DataChunks[0].pBuffer, (int)asyncResult.m_DataChunks[0].BufferLength); } }
603                     }
604                 }
605                 GlobalLog.Print("HttpResponseStreamAsyncResult#" + ValidationHelper.HashString(asyncResult) + "::Callback() calling Complete()");
606             }
607             catch (Exception e) {
608                 result = e;
609             }
610             asyncResult.InvokeCallback(result);
611         }
612 
Callback(uint errorCode, uint numBytes, NativeOverlapped* nativeOverlapped)613         private static unsafe void Callback(uint errorCode, uint numBytes, NativeOverlapped* nativeOverlapped) {
614             Overlapped callbackOverlapped = Overlapped.Unpack(nativeOverlapped);
615             HttpResponseStreamAsyncResult asyncResult = callbackOverlapped.AsyncResult as HttpResponseStreamAsyncResult;
616             GlobalLog.Print("HttpResponseStreamAsyncResult#" + ValidationHelper.HashString(asyncResult) + "::Callback() errorCode:0x" + errorCode.ToString("x8") + " numBytes:" + numBytes + " nativeOverlapped:0x" + ((IntPtr)nativeOverlapped).ToString("x8"));
617 
618             IOCompleted(asyncResult, errorCode, numBytes);
619         }
620 
621         // Will be called from the base class upon InvokeCallback()
Cleanup()622         protected override void Cleanup() {
623             base.Cleanup();
624             if (m_pOverlapped != null) {
625                 Overlapped.Free(m_pOverlapped);
626             }
627         }
628 
629     }
630 
631 }
632