Code:
/ DotNET / DotNET / 8.0 / untmp / whidbey / REDBITS / ndp / fx / src / Net / System / Net / _ConnectStream.cs / 3 / _ConnectStream.cs
//------------------------------------------------------------------------------ //// Copyright (c) Microsoft Corporation. All rights reserved. // //----------------------------------------------------------------------------- namespace System.Net { using System.Diagnostics; using System.IO; using System.Net.Sockets; using System.Runtime.InteropServices; using System.Threading; using System.Security.Permissions; using System.ComponentModel; internal struct WriteHeadersCallbackState{ internal HttpWebRequest request; internal ConnectStream stream; internal WriteHeadersCallbackState(HttpWebRequest request, ConnectStream stream){ this.request = request; this.stream = stream; } } /*++ ConnectStream - a stream interface to a Connection object. This class implements the Stream interface, as well as a WriteHeaders call. Inside this stream we handle details like chunking, dechunking and tracking of ContentLength. To write or read data, we call a method on the connection object. The connection object is responsible for protocol stuff that happens 'below' the level of the HTTP request, for example MUX or SSL --*/ internal class ConnectStream : Stream, ICloseEx { #if DEBUG internal IAsyncResult _PendingResult; #endif private static class Nesting { public const int Idle = 0; public const int IoInProgress = 1; // we are doing read or write public const int Closed = 2; // stream was closed if that is done in IoInProgress on write, the write will resume delayed close part. public const int InError = 3; // IO is not allowed due to error stream state public const int InternalIO = 4; // stream is used by us, this is internal error if public IO sees that value } private int m_CallNesting; // see Nesting enum for details private ScatterGatherBuffers m_BufferedData; // list of sent buffers in case of resubmit (redirect/authentication). private bool m_SuppressWrite; // don't write data to the connection, only buffer it private bool m_BufferOnly; // don't write data to the connection, only buffer it private long m_BytesLeftToWrite; // Total bytes left to be written. private int m_BytesAlreadyTransferred; // Bytes already read/written in the current operation. private Connection m_Connection; // Connection for I/O. private byte[] m_ReadBuffer; // Read buffer for read stream. private int m_ReadOffset; // Offset into m_ReadBuffer. private int m_ReadBufferSize; // Bytes left in m_ReadBuffer. private long m_ReadBytes; // Total bytes to read on stream, -1 for read to end. private bool m_Chunked; // True if we're doing chunked read. private int m_DoneCalled; // 0 at init, 1 after we've called Read/Write Done private int m_ShutDown; // 0 at init, 1 after we've called Complete private Exception m_ErrorException; // non-null if we've seen an error on this connection. private bool m_ChunkEofRecvd; // True, if we've seen an EOF, or reached a EOF state for no more reads private int m_ChunkSize; // Number of bytes in current chunk. private byte[] m_TempBuffer; // A temporary buffer. private bool m_ChunkedNeedCRLFRead; // true - when we need to read a /r/n before a chunk size private bool m_Draining; // true - when we're draining. needed to handle chunked draining. private HttpWriteMode m_HttpWriteMode; private int m_ReadTimeout; // timeout in ms for reads private int m_WriteTimeout; // timeout in ms for writes private const long c_MaxDrainBytes = 64 * 1024; // 64 K - greater than, we should just close the connection private static readonly WaitCallback m_ReadChunkedCallbackDelegate = new WaitCallback(ReadChunkedCallback); private static readonly AsyncCallback m_ReadCallbackDelegate = new AsyncCallback(ReadCallback); private static readonly AsyncCallback m_WriteCallbackDelegate = new AsyncCallback(WriteCallback); private static readonly AsyncCallback m_WriteHeadersCallback = new AsyncCallback(WriteHeadersCallback); // Special value indicating that an asynchronous read operation is intentionally zero-length. private static readonly object ZeroLengthRead = new object(); private HttpWebRequest m_Request; // // Timeout - timeout in ms for sync reads & writes, passed in HttpWebRequest // public override bool CanTimeout { get {return true;} } public override int ReadTimeout { get { return m_ReadTimeout; } set { if (value<=0 && value!=System.Threading.Timeout.Infinite) { throw new ArgumentOutOfRangeException(SR.GetString(SR.net_io_timeout_use_gt_zero)); } m_ReadTimeout = value; } } public override int WriteTimeout { get { return m_WriteTimeout; } set { if (value<=0 && value!=System.Threading.Timeout.Infinite) { throw new ArgumentOutOfRangeException(SR.GetString(SR.net_io_timeout_use_gt_zero)); } m_WriteTimeout = value; } } // // If IgnoreSocketErrors==true then no data will be sent to the wire // private bool m_IgnoreSocketErrors; internal bool IgnoreSocketErrors { get { return m_IgnoreSocketErrors; } } // // If the KeepAlive=true then we must be prepares for a write socket errors trying to flush the body // If the KeepAlive=false then we should cease body writing as the connection is probably dead // If fatal=true then the connection is dead due to IO fault (discovered during read), throw IO exception // // m_IgnoreSocketErrors and m_ThrowSocketError are mostly for a write type of streams. // However a response read stream may have this member set when draning a response on resubmit. // // This this isn't synchronized, we also check after receiving an exception from the transport whether these have been set // and take them into account if they have (on writes). private bool m_ErrorResponseStatus; internal void ErrorResponseNotify(bool isKeepAlive) { m_ErrorResponseStatus = true; m_IgnoreSocketErrors |= !isKeepAlive; GlobalLog.Print((WriteStream?"Write-":"Read-") + "ConnectStream#"+ ValidationHelper.HashString(this) + "::Got notification on an Error Response, m_IgnoreSocketErrors:" + m_IgnoreSocketErrors); } // This means we should throw a connection closed exception from now on (write only). // It's unclear whether this needs to be better synchronized with m_ErrorResponseStatus, such as if ErrorResponseNotify // were called (asynchronously) while a m_ErrorException was already set. internal void FatalResponseNotify() { if (m_ErrorException == null) { Interlocked.CompareExchange(ref m_ErrorException, new IOException(SR.GetString(SR.net_io_readfailure, SR.GetString(SR.net_io_connectionclosed))), null); } m_ErrorResponseStatus = false; GlobalLog.Print((WriteStream ? "Write-" : "Read-") + "ConnectStream#" + ValidationHelper.HashString(this) + "::Got notification on a Fatal Response"); } /*++ Write Constructor for this class. This is the write constructor; it takes as a parameter the amount of entity body data to be written, with a value of -1 meaning to write it out as chunked. The other parameter is the Connection of which we'll be writing. Right now we use the DefaultBufferSize for the stream. In the future we'd like to pass a 0 and have the stream be unbuffered for write. Input: Conn - Connection for this stream. BytesToWrite - Total bytes to be written, or -1 if we're doing chunked encoding. Returns: Nothing. --*/ public ConnectStream(Connection connection, HttpWebRequest request) { GlobalLog.Print("ConnectStream#" + ValidationHelper.HashString(this) + "::.ctor(Write)"); m_Connection = connection; m_ReadTimeout = m_WriteTimeout = System.Threading.Timeout.Infinite; // // we need to save a reference to the request for two things // 1. In case of buffer-only we kick in actual submition when the stream is closed by a user // 2. In case of write stream abort() we notify the request so the response stream is handled properly // m_Request = request; m_HttpWriteMode = request.HttpWriteMode; GlobalLog.Assert(m_HttpWriteMode != HttpWriteMode.Unknown, "ConnectStream#{0}::.ctor()|HttpWriteMode:{1}", ValidationHelper.HashString(this), m_HttpWriteMode); m_BytesLeftToWrite = m_HttpWriteMode==HttpWriteMode.ContentLength ? request.ContentLength : -1; if (request.HttpWriteMode==HttpWriteMode.Buffer) { m_BufferOnly = true; EnableWriteBuffering(); } GlobalLog.Print("ConnectStream#" + ValidationHelper.HashString(this) + "::.ctor() Connection:" + ValidationHelper.HashString(m_Connection) + " BytesToWrite:" + BytesLeftToWrite); } /*++ Read constructor for this class. This constructor takes in the connection and some information about a buffer that already contains data. Reads from this stream will read first from the buffer, and after that is exhausted will read from the connection. We also take in a size of bytes to read, or -1 if we're to read to connection close, and a flag indicating whether or now we're chunked. Input: Conn - Connection for this stream. buffer - Initial buffer to read from. offset - offset into buffer to start reading. size - number of bytes in buffer to read. readSize - Number of bytes allowed to be read from the stream, -1 for read to connection close. chunked - True if we're doing chunked decoding. Returns: Nothing. --*/ public ConnectStream(Connection connection, byte[] buffer, int offset, int bufferCount, long readCount, bool chunked, HttpWebRequest request) { GlobalLog.Print("ConnectStream#" + ValidationHelper.HashString(this) + "::.ctor(Read)"); if(Logging.On)Logging.PrintInfo(Logging.Web, this, "ConnectStream", SR.GetString(SR.net_log_buffered_n_bytes, readCount)); m_ReadBuffer = buffer; m_ReadOffset = offset; m_ReadBufferSize = bufferCount; m_ReadBytes = readCount; m_ReadTimeout = m_WriteTimeout = System.Threading.Timeout.Infinite; m_Chunked = chunked; m_Connection = connection; m_TempBuffer = new byte[2]; // // A request reference is used to verify (by the connection class) that this request should start a next one on Close. // m_Request = request; GlobalLog.Print("ConnectStream#" + ValidationHelper.HashString(this) + "::.ctor() Connection:" + ValidationHelper.HashString(m_Connection) + " m_ReadOffset:" + m_ReadOffset + " m_ReadBufferSize: " + m_ReadBufferSize + " ContentLength: " + m_ReadBytes + " m_Chunked:" + m_Chunked.ToString()); } internal void SwitchToContentLength(){ m_HttpWriteMode = HttpWriteMode.ContentLength; } internal bool SuppressWrite { /* Consider Removing get { return m_SuppressWrite; } */ set{ m_SuppressWrite = value; } } internal Connection Connection { get { return m_Connection; } } internal bool BufferOnly { get { return m_BufferOnly; } } internal ScatterGatherBuffers BufferedData { get { return m_BufferedData; } set { m_BufferedData = value; } } private bool WriteChunked { get { return m_HttpWriteMode==HttpWriteMode.Chunked; } } internal long BytesLeftToWrite { get { return m_BytesLeftToWrite; } } // True if this is a write stream. bool WriteStream { get { return m_HttpWriteMode != HttpWriteMode.Unknown; } } internal bool IsPostStream { get { return m_HttpWriteMode != HttpWriteMode.None; } } /*++ ErrorInStream - indicates an exception was caught internally due to a stream error, and that I/O operations should not continue Input: Nothing. Returns: True if there is an error --*/ internal bool ErrorInStream { get { return m_ErrorException!=null; } } /*++ CallDone - calls out to the Connection that spawned this Stream (using the DoneRead/DoneWrite method). If the Connection specified that we don't need to do this, or if we've already done this already, then we return silently. Input: Nothing. Returns: Nothing. --*/ internal void CallDone() { CallDone(null); } private void CallDone(ConnectionReturnResult returnResult) { GlobalLog.Enter("ConnectStream#" + ValidationHelper.HashString(this) + "::CallDone"); if ( Interlocked.Increment( ref m_DoneCalled) == 1 ) { if (!WriteStream) { #if DEBUG GlobalLog.DebugRemoveRequest(m_Request); #endif if (returnResult == null) { //readstartnextrequest will call setresponses internally. m_Connection.ReadStartNextRequest(m_Request, ref returnResult); } else{ ConnectionReturnResult.SetResponses(returnResult); } } else { m_Request.WriteCallDone(this, returnResult); } } GlobalLog.Leave("ConnectStream#" + ValidationHelper.HashString(this) + "::CallDone"); } internal void ProcessWriteCallDone(ConnectionReturnResult returnResult) { GlobalLog.Print("ConnectStream#" + ValidationHelper.HashString(this) + "::ProcessWriteCallDone()"); try { if (returnResult == null) { m_Connection.WriteStartNextRequest(m_Request, ref returnResult); // If the request is Sync, then we do our Read here for data if (!m_Request.Async) { object syncReaderResult = m_Request.ConnectionReaderAsyncResult.InternalWaitForCompletion(); //we should only do a syncread if we didn't already read the response //via poll when we handed back the request stream if (syncReaderResult == null && !m_Request.SawInitialResponse) #if DEBUG // Remove once mixed sync/async requests are supported. using (GlobalLog.SetThreadKind(ThreadKinds.Sync)) #endif { m_Connection.SyncRead(m_Request, true, false); } } m_Request.SawInitialResponse = false; } ConnectionReturnResult.SetResponses(returnResult); } finally { // This will decrement the response window on the write side AND may // result in either immediate or delayed processing of a response for the m_Request instance if (IsPostStream || m_Request.Async) m_Request.CheckWriteSideResponseProcessing(); } } internal bool IsClosed { get { return m_ShutDown != 0; } } /*++ Read property for this class. We return the readability of this stream. This is a read only property. Input: Nothing. Returns: True if this is a read stream, false otherwise. --*/ public override bool CanRead { get { return !WriteStream && !IsClosed; } } /*++ Seek property for this class. Since this stream is not seekable, we just return false. This is a read only property. Input: Nothing. Returns: false --*/ public override bool CanSeek { get { return false; } } /*++ CanWrite property for this class. We return the writeability of this stream. This is a read only property. Input: Nothing. Returns: True if this is a write stream, false otherwise. --*/ public override bool CanWrite { get { return WriteStream && !IsClosed; } } /*++ DataAvailable property for this class. This property check to see if at least one byte of data is currently available. This is a read only property. Input: Nothing. Returns: True if data is available, false otherwise. --*/ /* // Consider removing. public bool DataAvailable { get { // // Data is available if this is not a write stream and either // we have data buffered or the underlying connection has // data. // return !WriteStream && (m_ReadBufferSize != 0 || m_Connection.DataAvailable); } } */ /*++ Length property for this class. Since we don't support seeking, this property just throws a NotSupportedException. Input: Nothing. Returns: Throws exception. --*/ public override long Length { get { throw new NotSupportedException(SR.GetString(SR.net_noseek)); } } /*++ Position property for this class. Since we don't support seeking, this property just throws a NotSupportedException. Input: Nothing. Returns: Throws exception. --*/ public override long Position { get { throw new NotSupportedException(SR.GetString(SR.net_noseek)); } set { throw new NotSupportedException(SR.GetString(SR.net_noseek)); } } /*++ Eof property to indicate when the read is no longer allowed, because all data has been already read from socket. Input: Nothing. Returns: true/false depending on whether we are complete --*/ private bool Eof { get { if (ErrorInStream) { return true; } else if (m_Chunked) { return m_ChunkEofRecvd; } else if (m_ReadBytes == 0) { return true; } else if (m_ReadBytes == -1) { return(m_DoneCalled > 0 && m_ReadBufferSize <= 0); } else { return false; } } } /* Consider Removing internal bool IsEverythingBuffered { get { return m_ReadBytes >= 0 && m_ReadBufferSize == m_ReadBytes; } } */ /*++ Uses an old Stream to resubmit buffered data using the current stream, this is used in cases of POST, or authentication, where we need to buffer upload data so that it can be resubmitted Input: OldStream - Old Stream that was previously used Returns: Nothing. --*/ // internal void ResubmitWrite(ConnectStream oldStream, bool suppressWrite) { GlobalLog.Enter("ConnectStream#" + ValidationHelper.HashString(this) + "::ResubmitWrite", ValidationHelper.HashString(oldStream)); GlobalLog.ThreadContract(ThreadKinds.Sync, "ConnectStream#" + ValidationHelper.HashString(this) + "::ResubmitWrite"); // // // // we're going to resubmit // try { Interlocked.CompareExchange(ref m_CallNesting, Nesting.InternalIO, Nesting.Idle); GlobalLog.Print("ConnectStream#" + ValidationHelper.HashString(this) + "::ResubmitWrite() Inc: " + m_CallNesting.ToString()); GlobalLog.Print("ConnectStream#" + ValidationHelper.HashString(this) + "::ResubmitWrite(), callNesting : " + m_CallNesting.ToString() + " IsClosed = " + IsClosed); // // no need to buffer here: // we're already resubmitting buffered data give it to the connection to put it on the wire again // we set BytesLeftToWrite to 0 'cause even on failure there can be no recovery, // so just leave it to IOError() to clean up and don't call ResubmitWrite() // ScatterGatherBuffers bufferedData = oldStream.BufferedData; SafeSetSocketTimeout(SocketShutdown.Send); if (!WriteChunked) { if (!suppressWrite) m_Connection.Write(bufferedData); } else { // we have the data buffered, but we still want to chunk. // first set this to disable Close() from sending a chunk terminator. GlobalLog.Assert(m_HttpWriteMode != HttpWriteMode.None, "ConnectStream#{0}::ResubmitWrite()|m_HttpWriteMode == HttpWriteMode.None", ValidationHelper.HashString(this)); m_HttpWriteMode = HttpWriteMode.ContentLength; if (bufferedData.Length==0) { m_Connection.Write(NclConstants.ChunkTerminator, 0, NclConstants.ChunkTerminator.Length); } else { int chunkHeaderOffset = 0; byte[] chunkHeaderBuffer = GetChunkHeader(bufferedData.Length, out chunkHeaderOffset); BufferOffsetSize[] dataBuffers = bufferedData.GetBuffers(); BufferOffsetSize[] buffers = new BufferOffsetSize[dataBuffers.Length + 3]; buffers[0] = new BufferOffsetSize(chunkHeaderBuffer, chunkHeaderOffset, chunkHeaderBuffer.Length - chunkHeaderOffset, false); int index = 0; foreach (BufferOffsetSize buffer in dataBuffers) { buffers[++index] = buffer; } buffers[++index] = new BufferOffsetSize(NclConstants.CRLF, 0, NclConstants.CRLF.Length, false); buffers[++index] = new BufferOffsetSize(NclConstants.ChunkTerminator, 0, NclConstants.ChunkTerminator.Length, false); SplitWritesState splitState = new SplitWritesState(buffers); BufferOffsetSize[] sendBuffers = splitState.GetNextBuffers(); while(sendBuffers != null){ m_Connection.MultipleWrite(sendBuffers); sendBuffers = splitState.GetNextBuffers(); } } } if(Logging.On && bufferedData.GetBuffers() != null) { foreach (BufferOffsetSize bufferOffsetSize in bufferedData.GetBuffers()) { if (bufferOffsetSize == null) { Logging.Dump(Logging.Web, this, "ResubmitWrite", null, 0, 0); } else { Logging.Dump(Logging.Web, this, "ResubmitWrite", bufferOffsetSize.Buffer, bufferOffsetSize.Offset, bufferOffsetSize.Size); } } } GlobalLog.Print("ConnectStream#" + ValidationHelper.HashString(this) + "::ResubmitWrite() sent:" + bufferedData.Length.ToString() ); } catch (Exception exception) { if (NclUtilities.IsFatal(exception)) throw; // A Fatal error WebException we = new WebException(NetRes.GetWebStatusString("net_connclosed", WebExceptionStatus.SendFailure), WebExceptionStatus.SendFailure, WebExceptionInternalStatus.RequestFatal, exception); IOError(we, false); } finally { Interlocked.CompareExchange(ref m_CallNesting, Nesting.Idle, Nesting.InternalIO); GlobalLog.Print("ConnectStream#" + ValidationHelper.HashString(this) + "::ResubmitWrite(), callNesting : " + m_CallNesting.ToString() + " IsClosed = " + IsClosed); } m_BytesLeftToWrite = 0; CallDone(); GlobalLog.Leave("ConnectStream#" + ValidationHelper.HashString(this) + "::ResubmitWrite", BytesLeftToWrite.ToString()); } // // called by HttpWebRequest if AllowWriteStreamBuffering is true on that instance // internal void EnableWriteBuffering() { GlobalLog.Assert(WriteStream, "ConnectStream#{0}::EnableWriteBuffering()|!WriteStream", ValidationHelper.HashString(this)); if (BufferedData==null) { // create stream on demand, only if needed GlobalLog.Print("ConnectStream#" + ValidationHelper.HashString(this) + "::EnableWriteBuffering() Write() creating ScatterGatherBuffers WriteChunked:" + WriteChunked.ToString() + " BytesLeftToWrite:" + BytesLeftToWrite.ToString()); if (WriteChunked) { BufferedData = new ScatterGatherBuffers(); } else { BufferedData = new ScatterGatherBuffers(BytesLeftToWrite); } } } /*++ FillFromBufferedData - This fills in a buffer from data that we have buffered. This method pulls out the buffered data that may have been provided as excess actual data from the header parsing Input: buffer - Buffer to read into. offset - Offset in buffer to read into. size - Size in bytes to read. Returns: Number of bytes read. --*/ private int FillFromBufferedData(byte [] buffer, ref int offset, ref int size ) { // // if there's no stuff in our read buffer just return 0 // if (m_ReadBufferSize == 0) { return 0; } // // There's stuff in our read buffer. Figure out how much to take, // which is the minimum of what we have and what we're to read, // and copy it out. // int BytesTransferred = Math.Min(size, m_ReadBufferSize); GlobalLog.Print("ConnectStream#" + ValidationHelper.HashString(this) + "::FillFromBufferedData() Filling bytes: " + BytesTransferred.ToString()); Buffer.BlockCopy( m_ReadBuffer, m_ReadOffset, buffer, offset, BytesTransferred); // Update our internal read buffer state with what we took. m_ReadOffset += BytesTransferred; m_ReadBufferSize -= BytesTransferred; // If the read buffer size has gone to 0, null out our pointer // to it so maybe it'll be garbage-collected faster. if (m_ReadBufferSize == 0) { m_ReadBuffer = null; } // Update what we're to read and the caller's offset. size -= BytesTransferred; offset += BytesTransferred; return BytesTransferred; } /*++ Write This function write data to the network. If we were given a definite content length when constructed, we won't write more than that amount of data to the network. If the caller tries to do that, we'll throw an exception. If we're doing chunking, we'll chunk it up before sending to the connection. Input: buffer - buffer to write. offset - offset in buffer to write from. size - size in bytes to write. Returns: Nothing. --*/ public override void Write(byte[] buffer, int offset, int size) { #if DEBUG using (GlobalLog.SetThreadKind(ThreadKinds.User | ThreadKinds.Sync)) { #endif if (Logging.On) Logging.Enter(Logging.Web, this, "Write", ""); // // Basic parameter validation // if (!WriteStream) { throw new NotSupportedException(SR.GetString(SR.net_readonlystream)); } if (buffer==null) { throw new ArgumentNullException("buffer"); } if (offset<0 || offset>buffer.Length) { throw new ArgumentOutOfRangeException("offset"); } if (size<0 || size>buffer.Length-offset) { throw new ArgumentOutOfRangeException("size"); } InternalWrite(false, buffer, offset, size, null, null ); if(Logging.On)Logging.Dump(Logging.Web, this, "Write", buffer, offset, size); if(Logging.On)Logging.Exit(Logging.Web, this, "Write", ""); #if DEBUG } #endif } /*++ BeginWrite - Does async write to the Stream Splits the operation into two outcomes, for the non-chunking case, we calculate size to write, then call BeginWrite on the Connection directly, and then we're finish, for the Chunked case, we procede with use two callbacks to continue the chunking after the first write, and then second write. In order that all of the Chunk data/header/terminator, in the correct format are sent. Input: buffer - Buffer to write into. offset - Offset in buffer to write into. size - Size in bytes to write. callback - the callback to be called on result state - object to be passed to callback Returns: IAsyncResult - the async result --*/ [HostProtection(ExternalThreading=true)] public override IAsyncResult BeginWrite(byte[] buffer, int offset, int size, AsyncCallback callback, object state ) { #if DEBUG using (GlobalLog.SetThreadKind(ThreadKinds.User | ThreadKinds.Async)) { #endif GlobalLog.Print("ConnectStream#" + ValidationHelper.HashString(this) + "::BeginWrite " + ValidationHelper.HashString(m_Connection) + ", " + offset.ToString() + ", " + size.ToString()); if(Logging.On)Logging.Enter(Logging.Web, this, "BeginWrite", ""); // // Basic parameter validation // if (!WriteStream) { throw new NotSupportedException(SR.GetString(SR.net_readonlystream)); } if (buffer==null) { throw new ArgumentNullException("buffer"); } if (offset<0 || offset>buffer.Length) { throw new ArgumentOutOfRangeException("offset"); } if (size<0 || size>buffer.Length-offset) { throw new ArgumentOutOfRangeException("size"); } IAsyncResult result = InternalWrite(true, buffer, offset, size, callback, state); if(Logging.On)Logging.Exit(Logging.Web, this, "BeginWrite", result); return result; #if DEBUG } #endif } // // Handles either async or sync Writing for *public* stream API // private IAsyncResult InternalWrite(bool async, byte[] buffer, int offset, int size, AsyncCallback callback, object state ) { // // if we have a stream error, or we've already shut down this socket // then we must prevent new BeginRead/BeginWrite's from getting // submited to the socket, since we've already closed the stream. // if (ErrorInStream) { GlobalLog.Print("ConnectStream#" + ValidationHelper.HashString(this) + "::InternalWrite() throwing:" + m_ErrorException.ToString()); throw m_ErrorException; } if (IsClosed && !IgnoreSocketErrors) { GlobalLog.Print("ConnectStream#" + ValidationHelper.HashString(this) + "::InternalWrite() throwing"); throw new WebException( NetRes.GetWebStatusString("net_requestaborted", WebExceptionStatus.ConnectionClosed), WebExceptionStatus.ConnectionClosed); } if (m_Request.Aborted && !IgnoreSocketErrors) { GlobalLog.Print("ConnectStream#" + ValidationHelper.HashString(this) + "::InternalWrite() throwing"); throw new WebException( NetRes.GetWebStatusString("net_requestaborted", WebExceptionStatus.RequestCanceled), WebExceptionStatus.RequestCanceled); } int nesting = Interlocked.CompareExchange(ref m_CallNesting, Nesting.IoInProgress, Nesting.Idle); GlobalLog.Print((async?"Async ":"") + "InternalWrite() In: callNesting : " + nesting.ToString()); if (nesting != Nesting.Idle && nesting != Nesting.Closed) { throw new NotSupportedException(SR.GetString(SR.net_no_concurrent_io_allowed)); } // // buffer data to the ScatterGatherBuffers // regardles of chunking, we buffer the data as if we were not chunking // and on resubmit, we don't bother chunking. // if (BufferedData!=null && size != 0 && (m_Request.ContentLength != 0 || !IsPostStream || !m_Request.NtlmKeepAlive)) { // // if we don't need to, we shouldn't send data on the wire as well // but in this case we gave a stream to the user so we have transport // BufferedData.Write(buffer, offset, size); } LazyAsyncResult asyncResult = null; bool completeSync = false; try { if (size == 0 || BufferOnly || m_SuppressWrite || IgnoreSocketErrors) { // // We're not putting this data on the wire, then we're done // if(m_SuppressWrite && m_BytesLeftToWrite > 0 && size > 0) { m_BytesLeftToWrite -= size; } /* SSS_WARNINGS_OFF */ GlobalLog.Print("ConnectStream#" + ValidationHelper.HashString(this) + "::InternalWrite() swallowing: size==0 || BufferOnly || IgnoreSocketErrors= " + (size==0) + BufferOnly + IgnoreSocketErrors); /* SSS_WARNINGS_ON */ if (async) { asyncResult = new LazyAsyncResult(this, state, callback); completeSync = true; } return asyncResult; } else if (WriteChunked) { // // We're chunking. Write the chunk header out first, // then the data, then a CRLF. // for this we'll use BeginMultipleSend(); // int chunkHeaderOffset = 0; byte[] chunkHeaderBuffer = GetChunkHeader(size, out chunkHeaderOffset); BufferOffsetSize[] buffers; GlobalLog.Print("ConnectStream#" + ValidationHelper.HashString(this) + "::InternalWrite() m_ErrorResponseStatus:" + m_ErrorResponseStatus); if (m_ErrorResponseStatus) { //if we already got a (>200) response, then just terminate chunking and //switch to simple buffering (if any) GlobalLog.Print("ConnectStream#" + ValidationHelper.HashString(this) + "::InternalWrite() setting m_IgnoreSocketErrors to True (was:" + m_IgnoreSocketErrors + ") sending chunk terminator"); m_IgnoreSocketErrors = true; buffers = new BufferOffsetSize[1]; buffers[0] = new BufferOffsetSize(NclConstants.ChunkTerminator, 0, NclConstants.ChunkTerminator.Length, false); } else { buffers = new BufferOffsetSize[3]; buffers[0] = new BufferOffsetSize(chunkHeaderBuffer, chunkHeaderOffset, chunkHeaderBuffer.Length - chunkHeaderOffset, false); buffers[1] = new BufferOffsetSize(buffer, offset, size, false); buffers[2] = new BufferOffsetSize(NclConstants.CRLF, 0, NclConstants.CRLF.Length, false); } asyncResult = (async) ? new NestedMultipleAsyncResult(this, state, callback, buffers) : null; // // after setting up the buffers and error checking do the async Write Call // try { if (async) { m_Connection.BeginMultipleWrite(buffers, m_WriteCallbackDelegate, asyncResult); } else { SafeSetSocketTimeout(SocketShutdown.Send); m_Connection.MultipleWrite(buffers); } } catch (Exception exception) { // IgnoreSocketErrors can be set at any time - need to check it again. if (IgnoreSocketErrors && !NclUtilities.IsFatal(exception)) { /* SSS_WARNINGS_OFF */ GlobalLog.Print("ConnectStream#" + ValidationHelper.HashString(this) + "::InternalWrite() swallowing: IgnoreSocketErrors set after throw."); /* SSS_WARNINGS_ON */ if (async) { completeSync = true; } return asyncResult; } if (m_Request.Aborted && (exception is IOException || exception is ObjectDisposedException)) { GlobalLog.Print("ConnectStream#" + ValidationHelper.HashString(this) + "::InternalWrite() throwing"); throw new WebException( NetRes.GetWebStatusString("net_requestaborted", WebExceptionStatus.RequestCanceled), WebExceptionStatus.RequestCanceled); } nesting = Nesting.InError; if (NclUtilities.IsFatal(exception)) { m_ErrorResponseStatus = false; IOError(exception); throw; } if (m_ErrorResponseStatus) { // We already got a error response, hence server could drop the connection, // Here we are recovering for future (optional) resubmit ... m_IgnoreSocketErrors = true; GlobalLog.Print("ConnectStream#" + ValidationHelper.HashString(this) + "::InternalWrite() IGNORE write fault"); if (async) { completeSync = true; } } else { // Note we could ignore this since receive callback is already posted and // should give us similar failure IOError(exception); GlobalLog.Print("ConnectStream#" + ValidationHelper.HashString(this) + "::InternalWrite() throwing:" + exception.ToString()); throw; } } GlobalLog.Print("ConnectStream#" + ValidationHelper.HashString(this) + "::InternalWrite chunked"); return asyncResult; } else { // // We're not chunking. See if we're sending too much; if not, // go ahead and write it. // asyncResult = (async) ? new NestedSingleAsyncResult(this, state, callback, buffer, offset, size) : null; if (BytesLeftToWrite != -1) { // // but only check if we aren't writing to an unknown content-length size, // as we can be buffering. // if (BytesLeftToWrite < (long)size) { // // writing too much data. // GlobalLog.Print("ConnectStream#" + ValidationHelper.HashString(this) + "::InternalWrite()"); throw new ProtocolViolationException(SR.GetString(SR.net_entitytoobig)); } if (!async) { // // Otherwise update our bytes left to send and send it. // m_BytesLeftToWrite -= (long)size; } } // // After doing, the m_WriteByte size calculations, and error checking // here doing the async Write Call // try { if (async) { if(m_Request.ContentLength == 0 && IsPostStream) { m_BytesLeftToWrite -=size; completeSync = true; } else{ m_BytesAlreadyTransferred = size; m_Connection.BeginWrite(buffer, offset, size, m_WriteCallbackDelegate, asyncResult); } } else { SafeSetSocketTimeout(SocketShutdown.Send); //If we are doing the ntlm handshake, contentlength //could be 0 for the first part, even if there is data //to write. if (m_Request.ContentLength != 0 || !IsPostStream || !m_Request.NtlmKeepAlive) { m_Connection.Write(buffer, offset, size); } } } catch (Exception exception) { // IgnoreSocketErrors can be set at any time - need to check it again. if (IgnoreSocketErrors && !NclUtilities.IsFatal(exception)) { /* SSS_WARNINGS_OFF */ GlobalLog.Print("ConnectStream#" + ValidationHelper.HashString(this) + "::InternalWrite() swallowing: IgnoreSocketErrors set after throw."); /* SSS_WARNINGS_ON */ if (async) { completeSync = true; } return asyncResult; } if (m_Request.Aborted && (exception is IOException || exception is ObjectDisposedException)) { GlobalLog.Print("ConnectStream#" + ValidationHelper.HashString(this) + "::InternalWrite() throwing"); throw new WebException( NetRes.GetWebStatusString("net_requestaborted", WebExceptionStatus.RequestCanceled), WebExceptionStatus.RequestCanceled); } nesting = Nesting.InError; if (NclUtilities.IsFatal(exception)) { m_ErrorResponseStatus = false; IOError(exception); throw; } if (m_ErrorResponseStatus) { // We already got a error response, hence server could drop the connection, // Here we are recovering for future (optional) resubmit ... m_IgnoreSocketErrors = true; GlobalLog.Print("ConnectStream#" + ValidationHelper.HashString(this) + "::InternWrite() IGNORE write fault"); if (async) { completeSync = true; } } else { // Note we could ignore this since receive callback is already posted and // should give us similar failure IOError(exception); GlobalLog.Print("ConnectStream#" + ValidationHelper.HashString(this) + "::InternalWrite() throwing:" + exception.ToString()); throw; } } GlobalLog.Print("ConnectStream#" + ValidationHelper.HashString(this) + "::InternalWrite"); return asyncResult; } } finally { if (!async || nesting == Nesting.InError || completeSync) { nesting = Interlocked.CompareExchange(ref m_CallNesting, (nesting == Nesting.InError? Nesting.InError: Nesting.Idle), Nesting.IoInProgress); GlobalLog.Print("InternalWrite() Out callNesting: " + nesting.ToString()); if (nesting == Nesting.Closed) { //send closing bytes ResumeInternalClose(asyncResult); } else if (completeSync && asyncResult != null) { asyncResult.InvokeCallback(); } } } } /*++ WriteDataCallback This is a callback, that is part of the main BeginWrite code, this is part of the normal transfer code. Input: asyncResult - IAsyncResult generated from BeginWrite Returns: None --*/ private static void WriteCallback(IAsyncResult asyncResult) { LazyAsyncResult userResult = (LazyAsyncResult) asyncResult.AsyncState; ((ConnectStream) userResult.AsyncObject).ProcessWriteCallback(asyncResult, userResult); } private void ProcessWriteCallback(IAsyncResult asyncResult, LazyAsyncResult userResult) { Exception userException = null; try { NestedSingleAsyncResult castedSingleAsyncResult = userResult as NestedSingleAsyncResult; if (castedSingleAsyncResult != null) { try { m_Connection.EndWrite(asyncResult); if (BytesLeftToWrite != -1) { // Update our bytes left to send. m_BytesLeftToWrite -= m_BytesAlreadyTransferred; m_BytesAlreadyTransferred = 0; } if (Logging.On) Logging.Dump(Logging.Web, this, "WriteCallback", castedSingleAsyncResult.Buffer, castedSingleAsyncResult.Offset, castedSingleAsyncResult.Size); } catch (Exception exception) { userException = exception; if (NclUtilities.IsFatal(exception)) { m_ErrorResponseStatus = false; IOError(exception); throw; } if (m_ErrorResponseStatus) { // We already got a error response, hence server could drop the connection, // Here we are recovering for future (optional) resubmit ... m_IgnoreSocketErrors = true; userException = null; GlobalLog.Print("ConnectStream#" + ValidationHelper.HashString(this) + "::EndWrite() IGNORE write fault"); } } } else { NestedMultipleAsyncResult castedMultipleAsyncResult = (NestedMultipleAsyncResult) userResult; try { m_Connection.EndMultipleWrite(asyncResult); if(Logging.On) { foreach (BufferOffsetSize bufferOffsetSize in castedMultipleAsyncResult.Buffers) { Logging.Dump(Logging.Web, castedMultipleAsyncResult, "WriteCallback", bufferOffsetSize.Buffer, bufferOffsetSize.Offset, bufferOffsetSize.Size); } } } catch (Exception exception) { userException = exception; if (NclUtilities.IsFatal(exception)) { m_ErrorResponseStatus = false; IOError(exception); throw; } if (m_ErrorResponseStatus) { // We already got a error response, hence server could drop the connection, // Here we are recovering for future (optional) resubmit ... m_IgnoreSocketErrors = true; userException = null; GlobalLog.Print("ConnectStream#" + ValidationHelper.HashString(this) + "::EndWrite() IGNORE write fault"); } } } } finally { if (Nesting.Closed == ExchangeCallNesting((userException == null? Nesting.Idle: Nesting.InError), Nesting.IoInProgress)) { if (userException != null && m_ErrorException == null) { Interlocked.CompareExchange (ref m_ErrorException, userException, null); } ResumeInternalClose(userResult); } else { userResult.InvokeCallback(userException); } } } //I need this because doing this within the static w/ "ref stream.m_Callnesting is getting an error. private int ExchangeCallNesting(int value, int comparand) { int result = Interlocked.CompareExchange(ref m_CallNesting, value, comparand); GlobalLog.Print("an AsyncCallback Out callNesting: " + m_CallNesting.ToString()); return result; } /*++ EndWrite - Finishes off async write of data, just calls into m_Connection.EndWrite to get the result. Input: asyncResult - The AsyncResult returned by BeginWrite --*/ public override void EndWrite(IAsyncResult asyncResult) { #if DEBUG using (GlobalLog.SetThreadKind(ThreadKinds.User)) { #endif GlobalLog.Enter("ConnectStream#" + ValidationHelper.HashString(this) + "::EndWrite"); if(Logging.On)Logging.Enter(Logging.Web, this, "EndWrite", ""); // // parameter validation // if (asyncResult==null) { throw new ArgumentNullException("asyncResult"); } LazyAsyncResult castedAsyncResult = asyncResult as LazyAsyncResult; if (castedAsyncResult==null || castedAsyncResult.AsyncObject!=this) { throw new ArgumentException(SR.GetString(SR.net_io_invalidasyncresult), "asyncResult"); } if (castedAsyncResult.EndCalled) { throw new InvalidOperationException(SR.GetString(SR.net_io_invalidendcall, "EndWrite")); } castedAsyncResult.EndCalled = true; // // wait & then check for errors // object returnValue = castedAsyncResult.InternalWaitForCompletion(); if (ErrorInStream) { GlobalLog.LeaveException("ConnectStream#" + ValidationHelper.HashString(this) + "::EndWrite", m_ErrorException); throw m_ErrorException; } Exception exception = returnValue as Exception; if (exception!=null) { if (exception is IOException && m_Request.Aborted) { GlobalLog.Print("ConnectStream#" + ValidationHelper.HashString(this) + "::InternalWrite() throwing"); throw new WebException( NetRes.GetWebStatusString("net_requestaborted", WebExceptionStatus.RequestCanceled), WebExceptionStatus.RequestCanceled); } IOError(exception); GlobalLog.LeaveException("ConnectStream#" + ValidationHelper.HashString(this) + "::EndWrite", exception); throw exception; } GlobalLog.Leave("ConnectStream#" + ValidationHelper.HashString(this) + "::EndWrite"); if(Logging.On)Logging.Exit(Logging.Web, this, "EndWrite", ""); #if DEBUG } #endif } /*++ Read - Read from the connection. ReadWithoutValidation This method reads from the network, or our internal buffer if there's data in that. If there's not, we'll read from the network. If we're doing chunked decoding, we'll decode it before returning from this call. Input: buffer - Buffer to read into. offset - Offset in buffer to read into. size - Size in bytes to read. Returns: Nothing. --*/ public override int Read([In, Out] byte[] buffer, int offset, int size) { #if DEBUG using (GlobalLog.SetThreadKind(ThreadKinds.User | ThreadKinds.Sync)) { #endif if (Logging.On) Logging.Enter(Logging.Web, this, "Read", ""); if (WriteStream) { throw new NotSupportedException(SR.GetString(SR.net_writeonlystream)); } if (buffer==null) { throw new ArgumentNullException("buffer"); } if (offset<0 || offset>buffer.Length) { throw new ArgumentOutOfRangeException("offset"); } if (size<0 || size>buffer.Length-offset) { throw new ArgumentOutOfRangeException("size"); } if (ErrorInStream) { throw m_ErrorException; } if (IsClosed) { throw new WebException( NetRes.GetWebStatusString("net_requestaborted", WebExceptionStatus.ConnectionClosed), WebExceptionStatus.ConnectionClosed); } if (m_Request.Aborted) { GlobalLog.Print("ConnectStream#" + ValidationHelper.HashString(this) + "::InternalWrite() throwing"); throw new WebException( NetRes.GetWebStatusString("net_requestaborted", WebExceptionStatus.RequestCanceled), WebExceptionStatus.RequestCanceled); } // // if we fail/hang this call for some reason, // this Nesting count we be non-0, so that when we // close this stream, we will abort the socket. // int nesting = Interlocked.CompareExchange(ref m_CallNesting, Nesting.IoInProgress, Nesting.Idle); GlobalLog.Print("Read() In: callNesting : " + m_CallNesting.ToString()); if (nesting != Nesting.Idle) { throw new NotSupportedException(SR.GetString(SR.net_no_concurrent_io_allowed)); } int bytesRead = -1; try { SafeSetSocketTimeout(SocketShutdown.Receive); } catch (Exception exception) { IOError(exception); throw; } try { bytesRead = ReadWithoutValidation(buffer, offset, size); } catch (Exception exception) { Win32Exception win32Exception = exception.InnerException as Win32Exception; if (win32Exception != null && win32Exception.NativeErrorCode == (int)SocketError.TimedOut) exception = new WebException(SR.GetString(SR.net_timeout), WebExceptionStatus.Timeout); throw exception; } Interlocked.CompareExchange(ref m_CallNesting, Nesting.Idle, Nesting.IoInProgress); GlobalLog.Print("Read() Out: callNesting: " + m_CallNesting.ToString()); if(Logging.On && bytesRead>0)Logging.Dump(Logging.Web, this, "Read", buffer, offset, bytesRead); if(Logging.On)Logging.Exit(Logging.Web, this, "Read", bytesRead); return bytesRead; #if DEBUG } #endif } /*++ ReadWithoutValidation - Read from the connection. Sync version of BeginReadWithoutValidation This method reads from the network, or our internal buffer if there's data in that. If there's not, we'll read from the network. If we're doing chunked decoding, we'll decode it before returning from this call. --*/ private int ReadWithoutValidation(byte[] buffer, int offset, int size) { return ReadWithoutValidation(buffer, offset, size, true); } // // abortOnError parameter is set to false when called from CloseInternal // private int ReadWithoutValidation([In, Out] byte[] buffer, int offset, int size, bool abortOnError) { GlobalLog.Print("int ConnectStream::ReadWithoutValidation()"); GlobalLog.Print("(start)m_ReadBytes = "+m_ReadBytes); // ********** WARNING - updating logic below should also be updated in BeginReadWithoutValidation ***************** // // Figure out how much we should really read. // int bytesToRead = 0; if (m_Chunked) { if (!m_ChunkEofRecvd) { // See if we have more left from a previous // chunk. if (m_ChunkSize != 0) { bytesToRead = Math.Min(size, m_ChunkSize); } else { // read size of next chunk try { bytesToRead = ReadChunkedSync(buffer, offset, size); m_ChunkSize -= bytesToRead; } catch (Exception exception) { if (abortOnError) { IOError(exception); } throw; } return bytesToRead; } } } else { // // Not doing chunked, so don't read more than is left. // if (m_ReadBytes != -1) { bytesToRead = (int)Math.Min(m_ReadBytes, (long)size); } else { bytesToRead = size; } } // If we're not going to read anything, either because they're // not asking for anything or there's nothing left, bail // out now. if (bytesToRead == 0 || this.Eof) { return 0; } try { bytesToRead = InternalRead(buffer, offset, bytesToRead); } catch (Exception exception) { if (abortOnError) { IOError(exception); } throw; } GlobalLog.Print("bytesTransferred = "+bytesToRead); int bytesTransferred = bytesToRead; if (m_Chunked && m_ChunkSize > 0) { if (bytesTransferred == 0) { // getting EOF in the middle of a chunk is a failure WebException we = new WebException( NetRes.GetWebStatusString("net_requestaborted", WebExceptionStatus.ConnectionClosed), WebExceptionStatus.ConnectionClosed); IOError(we, true); throw we; } m_ChunkSize -= bytesTransferred; } else { bool doneReading = false; if (bytesTransferred <= 0) { bytesTransferred = 0; // // We read 0 bytes from the connection, or got an error. This is OK if we're // reading to end, it's an error otherwise. // if (m_ReadBytes != -1) { // A Fatal error if (abortOnError) { IOError(null, false); // request will be aborted but the user will see EOF on that stream read call } else { throw m_ErrorException; // CloseInternal will process this case as abnormal } } else { // // We're reading to end, and we found the end, by reading 0 bytes // doneReading = true; } } // // Not chunking. Update our read bytes state and return what we've read. // if (m_ReadBytes != -1) { m_ReadBytes -= bytesTransferred; GlobalLog.Assert(m_ReadBytes >= 0, "ConnectStream: Attempting to read more bytes than available.|m_ReadBytes < 0"); GlobalLog.Print("m_ReadBytes = "+m_ReadBytes); if (m_ReadBytes < 0) throw new InternalException(); // } if (m_ReadBytes == 0 || doneReading) { // We're all done reading, tell the connection that. m_ReadBytes = 0; // // indicate to cache that read completed OK // CallDone(); } } GlobalLog.Print("bytesTransferred = "+bytesToRead); GlobalLog.Print("(end)m_ReadBytes = "+m_ReadBytes); // ********** WARNING - updating logic above should also be updated in BeginReadWithoutValidation and EndReadWithoutValidation ***************** return bytesTransferred; } /*++ BeginRead - Read from the connection. BeginReadWithoutValidation This method reads from the network, or our internal buffer if there's data in that. If there's not, we'll read from the network. If we're doing chunked decoding, we'll decode it before returning from this call. Input: buffer - Buffer to read into. offset - Offset in buffer to read into. size - Size in bytes to read. Returns: Nothing. --*/ [HostProtection(ExternalThreading=true)] public override IAsyncResult BeginRead(byte[] buffer, int offset, int size, AsyncCallback callback, object state) { #if DEBUG using (GlobalLog.SetThreadKind(ThreadKinds.User | ThreadKinds.Async)) { #endif GlobalLog.Print("ConnectStream#" + ValidationHelper.HashString(this) + "::BeginRead() " + ValidationHelper.HashString(m_Connection) + ", " + offset.ToString() + ", " + size.ToString()); if(Logging.On)Logging.Enter(Logging.Web, this, "BeginRead", ""); // // parameter validation // if (WriteStream) { throw new NotSupportedException(SR.GetString(SR.net_writeonlystream)); } if (buffer==null) { throw new ArgumentNullException("buffer"); } if (offset<0 || offset>buffer.Length) { throw new ArgumentOutOfRangeException("offset"); } if (size<0 || size>buffer.Length-offset) { throw new ArgumentOutOfRangeException("size"); } // // if we have a stream error, or we've already shut down this socket // then we must prevent new BeginRead/BeginWrite's from getting // submited to the socket, since we've already closed the stream. // if (ErrorInStream) { throw m_ErrorException; } if (IsClosed) { throw new WebException( NetRes.GetWebStatusString("net_requestaborted", WebExceptionStatus.ConnectionClosed), WebExceptionStatus.ConnectionClosed); } if (m_Request.Aborted) { GlobalLog.Print("ConnectStream#" + ValidationHelper.HashString(this) + "::InternalWrite() throwing"); throw new WebException( NetRes.GetWebStatusString("net_requestaborted", WebExceptionStatus.RequestCanceled), WebExceptionStatus.RequestCanceled); } // // if we fail/hang this call for some reason, // this Nesting count we be non-0, so that when we // close this stream, we will abort the socket. // int nesting = Interlocked.CompareExchange(ref m_CallNesting, Nesting.IoInProgress, Nesting.Idle); GlobalLog.Print("BeginRead() In: callNesting : " + m_CallNesting.ToString()); if (nesting != 0) { throw new NotSupportedException(SR.GetString(SR.net_no_concurrent_io_allowed)); } IAsyncResult result = BeginReadWithoutValidation( buffer, offset, size, callback, state); if(Logging.On)Logging.Exit(Logging.Web, this, "BeginRead", result); return result; #if DEBUG } #endif } /*++ BeginReadWithoutValidation - Read from the connection. internal version of BeginRead above, without validation This method reads from the network, or our internal buffer if there's data in that. If there's not, we'll read from the network. If we're doing chunked decoding, we'll decode it before returning from this call. Input: buffer - Buffer to read into. offset - Offset in buffer to read into. size - Size in bytes to read. Returns: Nothing. --*/ private IAsyncResult BeginReadWithoutValidation(byte[] buffer, int offset, int size, AsyncCallback callback, object state) { GlobalLog.Enter("ConnectStream#" + ValidationHelper.HashString(this) + "::BeginReadWithoutValidation", ValidationHelper.HashString(m_Connection) + ", " + offset.ToString() + ", " + size.ToString()); GlobalLog.ThreadContract(ThreadKinds.Unknown, "ConnectStream#" + ValidationHelper.HashString(this) + "::BeginReadWithoutValidation"); // // Figure out how much we should really read. // int bytesToRead = 0; if (m_Chunked) { if (!m_ChunkEofRecvd) { // See if we have more left from a previous // chunk. if (m_ChunkSize != 0) { bytesToRead = Math.Min(size, m_ChunkSize); } else { NestedSingleAsyncResult castedAsyncResult = new NestedSingleAsyncResult(this, state, callback, buffer, offset, size); // otherwise we queue a work item to parse the chunk // Consider: // Will we have an issue of thread dying off if we make a IO Read from that thread??? // ThreadPool.QueueUserWorkItem(m_ReadChunkedCallbackDelegate, castedAsyncResult); GlobalLog.Leave("ConnectStream#" + ValidationHelper.HashString(this) + "::BeginReadWithoutValidation() called QueueUserWorkItem(m_ReadChunkedCallbackDelegate)"); return castedAsyncResult; } } } else { // // Not doing chunked, so don't read more than is left. // if (m_ReadBytes != -1) { bytesToRead = (int)Math.Min(m_ReadBytes, (long)size); } else { bytesToRead = size; } } // If we're not going to read anything, either because they're // not asking for anything or there's nothing left, bail // out now. if (bytesToRead == 0 || this.Eof) { NestedSingleAsyncResult completedResult = new NestedSingleAsyncResult(this, state, callback, ZeroLengthRead); GlobalLog.Leave("ConnectStream#" + ValidationHelper.HashString(this) + "::BeginReadWithoutValidation() completed, bytesToRead: " + bytesToRead + " Eof: " + this.Eof.ToString()); return completedResult; } try { int bytesAlreadyRead = 0; if (m_ReadBufferSize > 0) { bytesAlreadyRead = FillFromBufferedData(buffer, ref offset, ref bytesToRead); if (bytesToRead == 0) { NestedSingleAsyncResult completedResult = new NestedSingleAsyncResult(this, state, callback, bytesAlreadyRead); GlobalLog.Leave("ConnectStream::BeginReadWithoutValidation"); return completedResult; } } if (ErrorInStream) { GlobalLog.LeaveException("ConnectStream::BeginReadWithoutValidation", m_ErrorException); throw m_ErrorException; } GlobalLog.Assert(m_DoneCalled == 0 || m_ReadBytes != -1, "BeginRead: Calling BeginRead after ReadDone.|m_DoneCalled > 0 && m_ReadBytes == -1"); // Keep track of this during the read so it can be added back at the end. m_BytesAlreadyTransferred = bytesAlreadyRead; IAsyncResult asyncResult = m_Connection.BeginRead(buffer, offset, bytesToRead, callback, state); // a null return indicates that the connection was closed underneath us. if (asyncResult == null) { m_BytesAlreadyTransferred = 0; m_ErrorException = new WebException( NetRes.GetWebStatusString("net_requestaborted", WebExceptionStatus.RequestCanceled), WebExceptionStatus.RequestCanceled); GlobalLog.LeaveException("ConnectStream::BeginReadWithoutValidation", m_ErrorException); throw m_ErrorException; } GlobalLog.Leave("ConnectStream#" + ValidationHelper.HashString(this) + "::BeginReadWithoutValidation() called BeginRead"); return asyncResult; } catch (Exception exception) { IOError(exception); GlobalLog.LeaveException("ConnectStream#" + ValidationHelper.HashString(this) + "::BeginReadWithoutValidation", exception); throw; } } /*++ InternalRead This is an interal version of Read without validation, that is called from the Chunked code as well the normal codepaths. --*/ private int InternalRead(byte[] buffer, int offset, int size) { GlobalLog.ThreadContract(ThreadKinds.Sync, "ConnectStream#" + ValidationHelper.HashString(this) + "::InternalRead"); // Read anything first out of the buffer int bytesToRead = FillFromBufferedData(buffer, ref offset, ref size); if (bytesToRead>0) { return bytesToRead; } // otherwise, we need to read more data from the connection. if (ErrorInStream) { GlobalLog.LeaveException("ConnectStream::InternalBeginRead", m_ErrorException); throw m_ErrorException; } bytesToRead = m_Connection.Read( buffer, offset, size); return bytesToRead; } /*++ ReadCallback This callback is only used by chunking as the last step of its multi-phase async operation. Input: asyncResult - IAsyncResult generated from BeginWrite Returns: None --*/ private static void ReadCallback(IAsyncResult asyncResult) { GlobalLog.Enter("ConnectStream::ReadCallback", "asyncResult=#"+ValidationHelper.HashString(asyncResult)); GlobalLog.ThreadContract(ThreadKinds.Unknown, "ConnectStream::ReadCallback"); // // we called m_Connection.BeginRead() previously that call // completed and called our internal callback // we passed the NestedSingleAsyncResult (that we then returned to the user) // as the state of this call, so build it back: // NestedSingleAsyncResult castedAsyncResult = (NestedSingleAsyncResult)asyncResult.AsyncState; ConnectStream thisConnectStream = (ConnectStream)castedAsyncResult.AsyncObject; try { int bytesTransferred = thisConnectStream.m_Connection.EndRead(asyncResult); if(Logging.On)Logging.Dump(Logging.Web, thisConnectStream, "ReadCallback", castedAsyncResult.Buffer, castedAsyncResult.Offset, Math.Min(castedAsyncResult.Size, bytesTransferred)); // // call the user's callback, with success // castedAsyncResult.InvokeCallback(bytesTransferred); } catch (Exception exception) { if (NclUtilities.IsFatal(exception)) throw; // // call the user's callback, with exception // castedAsyncResult.InvokeCallback(exception); } GlobalLog.Leave("ConnectStream::ReadCallback"); } /*++ EndRead - Finishes off the Read for the Connection EndReadWithoutValidation This method completes the async call created from BeginRead, it attempts to determine how many bytes were actually read, and if any errors occured. Input: asyncResult - created by BeginRead Returns: int - size of bytes read, or < 0 on error --*/ public override int EndRead(IAsyncResult asyncResult) { #if DEBUG using (GlobalLog.SetThreadKind(ThreadKinds.User)) { #endif if (Logging.On) Logging.Enter(Logging.Web, this, "EndRead", ""); // // parameter validation // if (asyncResult==null) { throw new ArgumentNullException("asyncResult"); } int bytesTransferred; bool zeroLengthRead = false; if (asyncResult.GetType() == typeof(NestedSingleAsyncResult)) { NestedSingleAsyncResult castedAsyncResult = (NestedSingleAsyncResult) asyncResult; if (castedAsyncResult.AsyncObject != this) { throw new ArgumentException(SR.GetString(SR.net_io_invalidasyncresult), "asyncResult"); } if (castedAsyncResult.EndCalled) { throw new InvalidOperationException(SR.GetString(SR.net_io_invalidendcall, "EndRead")); } castedAsyncResult.EndCalled = true; if (ErrorInStream) { GlobalLog.LeaveException("ConnectStream::EndRead", m_ErrorException); throw m_ErrorException; } object result = castedAsyncResult.InternalWaitForCompletion(); Exception errorException = result as Exception; if (errorException != null) { IOError(errorException, false); bytesTransferred = -1; } else { // If it's a NestedSingleAsyncResult, we completed it ourselves with our own result. if (result == null) { bytesTransferred = 0; } else if (result == ZeroLengthRead) { bytesTransferred = 0; zeroLengthRead = true; } else { try { bytesTransferred = (int) result; } catch (InvalidCastException) { bytesTransferred = -1; } } } } else { // If it's not a NestedSingleAsyncResult, we forwarded directly to the Connection and need to call EndRead. try { bytesTransferred = m_Connection.EndRead(asyncResult); } catch (Exception exception) { if (NclUtilities.IsFatal(exception)) throw; IOError(exception, false); bytesTransferred = -1; } } bytesTransferred = EndReadWithoutValidation(bytesTransferred, zeroLengthRead); Interlocked.CompareExchange(ref m_CallNesting, Nesting.Idle, Nesting.IoInProgress); GlobalLog.Print("EndRead() callNesting: " + m_CallNesting.ToString()); if(Logging.On)Logging.Exit(Logging.Web, this, "EndRead", bytesTransferred); if (m_ErrorException != null) { throw (m_ErrorException); } return bytesTransferred; #if DEBUG } #endif } /*++ EndReadWithoutValidation - Finishes off the Read for the Connection Called internally by EndRead. This method completes the async call created from BeginRead, it attempts to determine how many bytes were actually read, and if any errors occured. Input: asyncResult - created by BeginRead Returns: int - size of bytes read, or < 0 on error --*/ private int EndReadWithoutValidation(int bytesTransferred, bool zeroLengthRead) { GlobalLog.Enter("ConnectStream#" + ValidationHelper.HashString(this) + "::EndReadWithoutValidation", bytesTransferred.ToString()); int bytesAlreadyTransferred = m_BytesAlreadyTransferred; m_BytesAlreadyTransferred = 0; if (m_Chunked) { if (bytesTransferred < 0) { IOError(null, false); bytesTransferred = 0; } if (bytesTransferred == 0 && m_ChunkSize > 0) { // getting EOF in the middle of a chunk is a failure WebException we = new WebException( NetRes.GetWebStatusString("net_requestaborted", WebExceptionStatus.ConnectionClosed), WebExceptionStatus.ConnectionClosed); IOError(we, true); throw we; } bytesTransferred += bytesAlreadyTransferred; m_ChunkSize -= bytesTransferred; } else { // // we're not chunking, a note about error // checking here, in some cases due to 1.0 // servers we need to read until 0 bytes, // or a server reset, therefore, we may need // ignore sockets errors // bool doneReading = false; // if its finished without async, just use what was read already from the buffer, // otherwise we call the Connection's EndRead to find out if (bytesTransferred <= 0) { // // We read 0 bytes from the connection, or it had an error. This is OK if we're // reading to end, it's an error otherwise. // if (m_ReadBytes != -1 && (bytesTransferred < 0 || !zeroLengthRead)) { IOError(null, false); } else { // // We're reading to end, and we found the end, by reading 0 bytes // doneReading = true; bytesTransferred = 0; } } bytesTransferred += bytesAlreadyTransferred; // // Not chunking. Update our read bytes state and return what we've read. // if (m_ReadBytes != -1) { m_ReadBytes -= bytesTransferred; GlobalLog.Assert(m_ReadBytes >= 0, "ConnectStream: Attempting to read more bytes than available.|m_ReadBytes < 0"); GlobalLog.Print("m_ReadBytes = "+m_ReadBytes); } if (m_ReadBytes == 0 || doneReading) { // We're all done reading, tell the connection that. m_ReadBytes = 0; // // indicate to cache that read completed OK // CallDone(); } } GlobalLog.Leave("ConnectStream#" + ValidationHelper.HashString(this) + "::EndRead", bytesTransferred); return bytesTransferred; } /*++ ReadSingleByte - Read a single byte from the stream. A utility function to read a single byte from the stream. Could be done via ReadCoreNormal, but this is slightly more efficient. Input: Returns: The byte read as an int, or -1 if we couldn't read. --*/ internal int ReadSingleByte() { if (ErrorInStream) { return -1; } if (m_ReadBufferSize != 0) { m_ReadBufferSize--; return (int)m_ReadBuffer[m_ReadOffset++]; } else { int bytesTransferred = m_Connection.Read(m_TempBuffer, 0, 1); if (bytesTransferred <= 0) { return -1; } return (int)m_TempBuffer[0]; } } /*++ ReadCRLF A utility routine that tries to read the CRLF at the end of a chunk. Input: buffer - buffer to read into Returns: int - number of bytes read --*/ private int ReadCRLF(byte[] buffer) { GlobalLog.Enter("ConnectStream#" + ValidationHelper.HashString(this) + "::ReadCRLF"); int offset = 0; int size = NclConstants.CRLF.Length; int BytesRead = FillFromBufferedData(buffer, ref offset, ref size); if (BytesRead >= 0 && BytesRead != NclConstants.CRLF.Length) { do { int bytesTransferred = m_Connection.Read(buffer, offset, size); if (bytesTransferred <= 0) { GlobalLog.Leave("ConnectStream#" + ValidationHelper.HashString(this) + "::ReadCRLF", bytesTransferred); throw new IOException(SR.GetString(SR.net_io_readfailure, SR.GetString(SR.net_io_connectionclosed))); } else { size -= bytesTransferred; offset += bytesTransferred; } } while ( size > 0 ); } GlobalLog.Leave("ConnectStream#" + ValidationHelper.HashString(this) + "::ReadCRLF", BytesRead); return BytesRead; } /*++ ReadChunkedCallback This is callback, that parses and does a chunked read. It is here that we attempt to Read enough bytes to determine the size of the next chunk of data, and parse through any headers/control information associated with that chunk. Input: asyncResult - IAsyncResult generated from ConnectStream.BeginRead --*/ // private static void ReadChunkedCallback(object state) { #if DEBUG GlobalLog.SetThreadSource(ThreadKinds.Worker); using (GlobalLog.SetThreadKind(ThreadKinds.System | ThreadKinds.Sync)) { #endif // ********** WARNING - updating logic below should also be updated in ReadChunkedSync ***************** NestedSingleAsyncResult castedAsyncResult = state as NestedSingleAsyncResult; ConnectStream thisConnectStream = castedAsyncResult.AsyncObject as ConnectStream; GlobalLog.Enter("ConnectStream#" + ValidationHelper.HashString(thisConnectStream) + "::ReadChunkedCallback", ValidationHelper.HashString(castedAsyncResult)); try { if (!thisConnectStream.m_Draining && thisConnectStream.IsClosed) { // throw on shutdown only if we're not draining the socket. Exception exception = new WebException( NetRes.GetWebStatusString("net_requestaborted", WebExceptionStatus.ConnectionClosed), WebExceptionStatus.ConnectionClosed); castedAsyncResult.InvokeCallback(exception); GlobalLog.LeaveException("ReadChunkedCallback", exception); return; } else if (thisConnectStream.m_ErrorException!=null) { // throw on IO error even if we're draining the socket. castedAsyncResult.InvokeCallback(thisConnectStream.m_ErrorException); GlobalLog.LeaveException("ReadChunkedCallback", thisConnectStream.m_ErrorException); return; } if (thisConnectStream.m_ChunkedNeedCRLFRead) { thisConnectStream.ReadCRLF(thisConnectStream.m_TempBuffer); thisConnectStream.m_ChunkedNeedCRLFRead = false; } StreamChunkBytes ReadByteBuffer = new StreamChunkBytes(thisConnectStream); // We need to determine size of next chunk, // by carefully reading, byte by byte thisConnectStream.m_ChunkSize = thisConnectStream.ProcessReadChunkedSize(ReadByteBuffer); // If this isn't a zero length chunk, read it. if (thisConnectStream.m_ChunkSize != 0) { thisConnectStream.m_ChunkedNeedCRLFRead = true; int bytesToRead = Math.Min(castedAsyncResult.Size, thisConnectStream.m_ChunkSize); // // Attempt to fill in our entired read from, // data previously buffered, if this completely // satisfies us, then we are done, complete sync // int bytesAlreadyRead = 0; if (thisConnectStream.m_ReadBufferSize > 0) { bytesAlreadyRead = thisConnectStream.FillFromBufferedData(castedAsyncResult.Buffer, ref castedAsyncResult.Offset, ref bytesToRead); if (bytesToRead == 0) { castedAsyncResult.InvokeCallback(bytesAlreadyRead); GlobalLog.Leave("ConnectStream::ReadChunkedCallback"); return; } } // // otherwise, we need to read more data from the connection. // if (thisConnectStream.ErrorInStream) { GlobalLog.LeaveException("ConnectStream::ReadChunkedCallback", thisConnectStream.m_ErrorException); throw thisConnectStream.m_ErrorException; } GlobalLog.Assert(thisConnectStream.m_DoneCalled == 0 || thisConnectStream.m_ReadBytes != -1, "ConnectStream::ReadChunkedCallback|Calling BeginRead after ReadDone."); // Keep track of this during the read so it can be added back at the end. thisConnectStream.m_BytesAlreadyTransferred = bytesAlreadyRead; IAsyncResult asyncResult = thisConnectStream.m_Connection.BeginRead(castedAsyncResult.Buffer, castedAsyncResult.Offset, bytesToRead, m_ReadCallbackDelegate, castedAsyncResult); // a null return indicates that the connection was closed underneath us. if (asyncResult == null) { thisConnectStream.m_BytesAlreadyTransferred = 0; thisConnectStream.m_ErrorException = new WebException( NetRes.GetWebStatusString("net_requestaborted", WebExceptionStatus.RequestCanceled), WebExceptionStatus.RequestCanceled); GlobalLog.LeaveException("ConnectStream::ReadChunkedCallback", thisConnectStream.m_ErrorException); throw thisConnectStream.m_ErrorException; } } else { // We've found the terminating 0 length chunk. We may be very well looking // at an extension footer or the very final CRLF. thisConnectStream.ReadCRLF(thisConnectStream.m_TempBuffer); thisConnectStream.RemoveTrailers(ReadByteBuffer); // Remember that we've found this, so we don't try and dechunk // more. thisConnectStream.m_ReadBytes = 0; thisConnectStream.m_ChunkEofRecvd = true; thisConnectStream.CallDone(); // we're done reading, return 0 bytes castedAsyncResult.InvokeCallback(0); } GlobalLog.Leave("ReadChunkedCallback"); } catch (Exception exception) { if (NclUtilities.IsFatal(exception)) throw; castedAsyncResult.InvokeCallback(exception); GlobalLog.LeaveException("ConnectStream::ReadChunkedCallback", exception); } // ********** WARNING - updating logic above should also be updated in ReadChunkedSync ***************** #if DEBUG } #endif } /*++ ReadChunkedSync Parses and does a chunked read. It is here that we attempt to Read enough bytes to determine the size of the next chunk of data, and parse through any headers/control information associated with that chunk. Returns: None --*/ private int ReadChunkedSync(byte[] buffer, int offset, int size) { GlobalLog.Print("ConnectStream#" + ValidationHelper.HashString(this) + "::ReadChunkedSync"); // ********** WARNING - updating logic below should also be updated in ReadChunkedCallback ***************** if (!m_Draining && IsClosed) { // throw on shutdown only if we're not draining the socket. Exception exception = new WebException( NetRes.GetWebStatusString("net_requestaborted", WebExceptionStatus.ConnectionClosed), WebExceptionStatus.ConnectionClosed); throw exception; } else if (m_ErrorException!=null) { // throw on IO error even if we're draining the socket. throw m_ErrorException; } if (m_ChunkedNeedCRLFRead) { ReadCRLF(m_TempBuffer); m_ChunkedNeedCRLFRead = false; } StreamChunkBytes ReadByteBuffer = new StreamChunkBytes(this); // We need to determine size of next chunk, // by carefully reading, byte by byte m_ChunkSize = ProcessReadChunkedSize(ReadByteBuffer); // If this isn't a zero length chunk, read it. if (m_ChunkSize != 0) { m_ChunkedNeedCRLFRead = true; return InternalRead(buffer, offset, Math.Min(size, m_ChunkSize)); } else { // We've found the terminating 0 length chunk. We may be very well looking // at an extension footer or the very final CRLF. ReadCRLF(m_TempBuffer); RemoveTrailers(ReadByteBuffer); // Remember that we've found this, so we don't try and dechunk // more. m_ReadBytes = 0; m_ChunkEofRecvd = true; CallDone(); // we're done reading, return 0 bytes return 0; } // ********** WARNING - updating logic above should also be updated in ReadChunkedAsync ***************** } /*++ ProcessReadChunkedSize This is a continuation of the ReadChunkedCallback, and is used to parse out the size of a chunk Input: TheByteRead - single byte read from wire to process castedAsyncResult - Async Chunked State information Returns: None --*/ private int ProcessReadChunkedSize(StreamChunkBytes ReadByteBuffer) { GlobalLog.Enter("ConnectStream#" + ValidationHelper.HashString(this) + "::ProcessReadChunkedSize"); // now get the chunk size. int chunkSize; int BytesRead = ChunkParse.GetChunkSize(ReadByteBuffer, out chunkSize); if (BytesRead <= 0) { GlobalLog.Leave("ConnectStream#" + ValidationHelper.HashString(this) + "::ProcessReadChunkedSize - error"); throw new IOException(SR.GetString(SR.net_io_readfailure, SR.GetString(SR.net_io_connectionclosed))); } // Now skip past and extensions and the CRLF. BytesRead = ChunkParse.SkipPastCRLF(ReadByteBuffer); if (BytesRead <= 0) { GlobalLog.Leave("ConnectStream#" + ValidationHelper.HashString(this) + "::ProcessReadChunkedSize - error"); throw new IOException(SR.GetString(SR.net_io_readfailure, SR.GetString(SR.net_io_connectionclosed))); } GlobalLog.Leave("ConnectStream#" + ValidationHelper.HashString(this) + "::ProcessReadChunkedSize", chunkSize); return chunkSize; } /*++ RemoveTrailers This handles possible trailer headers that are found after the last chunk. Currently we throw them away for this version. Input: ReadByteBuffer - Returns: None - throws on exception --*/ private void RemoveTrailers(StreamChunkBytes ReadByteBuffer) { while (m_TempBuffer[0] != '\r' && m_TempBuffer[1] != '\n') { int BytesRead = ChunkParse.SkipPastCRLF(ReadByteBuffer); if (BytesRead <= 0) { throw new IOException(SR.GetString(SR.net_io_readfailure, SR.GetString(SR.net_io_connectionclosed))); } ReadCRLF(m_TempBuffer); } } private static void WriteHeadersCallback(IAsyncResult ar) { if(ar.CompletedSynchronously){ return; } WriteHeadersCallbackState state = (WriteHeadersCallbackState)ar.AsyncState; ConnectStream stream = state.stream; HttpWebRequest request = state.request; WebExceptionStatus error = WebExceptionStatus.SendFailure; //m_Request.writebuffer may be set to null on resubmit before method exits byte[] writeBuffer = request.WriteBuffer; GlobalLog.Enter("ConnectStream#" + ValidationHelper.HashString(stream) + "::WriteHeadersCallback", "Connection#" + ValidationHelper.HashString(stream.m_Connection) + ", " + writeBuffer.Length.ToString()); try{ stream.m_Connection.EndWrite(ar); stream.m_Connection.CheckStartReceive(request); if (stream.m_Connection.m_InnerException != null) throw stream.m_Connection.m_InnerException; else error = WebExceptionStatus.Success; } catch (Exception e){ if (NclUtilities.IsFatal(e)) throw; GlobalLog.Print("ConnectStream#" + ValidationHelper.HashString(stream) + "::WriteHeaders Exception: "+e.ToString()); if (e is IOException || e is ObjectDisposedException) { //new connection but reset from server on inital send if(!stream.m_Connection.AtLeastOneResponseReceived && !request.BodyStarted){ e = new WebException( NetRes.GetWebStatusString("net_connclosed", error), error, WebExceptionInternalStatus.Recoverable, e); } else{ e = new WebException( NetRes.GetWebStatusString("net_connclosed", error), error, stream.m_Connection.AtLeastOneResponseReceived ? WebExceptionInternalStatus.Isolated : WebExceptionInternalStatus.RequestFatal, e); } } stream.IOError(e, false); } stream.ExchangeCallNesting(Nesting.Idle, Nesting.InternalIO); request.WriteHeadersCallback(error, stream, true); GlobalLog.Leave("ConnectStream#" + ValidationHelper.HashString(stream) + "::WriteHeadersCallback",writeBuffer.Length.ToString()); } /*++ WriteHeaders This function writes header data to the network. Headers are special in that they don't have any non-header transforms applied to them, and are not subject to content-length constraints. We just write them through, and if we're done writing headers we tell the connection that. Returns: WebExceptionStatus.Pending - we don't have a stream yet. WebExceptionStatus.SendFailure - there was an error while writing to the wire. WebExceptionStatus.Success - success. --*/ internal void WriteHeaders(bool async) { GlobalLog.Enter("ConnectStream#" + ValidationHelper.HashString(this) + "::WriteHeaders", "Connection#" + ValidationHelper.HashString(m_Connection) + ", headers buffer size = " + m_Request.WriteBuffer.Length.ToString()); WebExceptionStatus error = WebExceptionStatus.SendFailure; if (!ErrorInStream) { //m_Request.WriteBuffer may be set to null on resubmit before method exits byte[] writeBuffer = m_Request.WriteBuffer; try { Interlocked.CompareExchange(ref m_CallNesting, Nesting.InternalIO, Nesting.Idle); GlobalLog.Print("WriteHeaders() callNesting: " + m_CallNesting.ToString()); if(async) { WriteHeadersCallbackState state = new WriteHeadersCallbackState(m_Request, this); IAsyncResult ar = m_Connection.UnsafeBeginWrite(writeBuffer,0,writeBuffer.Length, m_WriteHeadersCallback, state); if (ar.CompletedSynchronously) { m_Connection.EndWrite(ar); m_Connection.CheckStartReceive(m_Request); error = WebExceptionStatus.Success; } else { error = WebExceptionStatus.Pending; #if DEBUG _PendingResult = ar; #endif } } else { SafeSetSocketTimeout(SocketShutdown.Send); m_Connection.Write(writeBuffer, 0, writeBuffer.Length); m_Connection.CheckStartReceive(m_Request); error = WebExceptionStatus.Success; } if (Logging.On) Logging.PrintInfo(Logging.Web, this, SR.GetString(SR.net_log_sending_headers, m_Request.Headers.ToString(true))); } catch (Exception e) { if (NclUtilities.IsFatal(e)) throw; GlobalLog.Print("ConnectStream#" + ValidationHelper.HashString(this) + "::WriteHeaders Exception: "+e.ToString()); if (e is IOException || e is ObjectDisposedException) { //new connection but reset from server on inital send if(!m_Connection.AtLeastOneResponseReceived && !m_Request.BodyStarted){ e = new WebException( NetRes.GetWebStatusString("net_connclosed", error), error, WebExceptionInternalStatus.Recoverable, e); } else{ e = new WebException( NetRes.GetWebStatusString("net_connclosed", error), error, m_Connection.AtLeastOneResponseReceived ? WebExceptionInternalStatus.Isolated : WebExceptionInternalStatus.RequestFatal, e); } } IOError(e, false); } finally { if(error != WebExceptionStatus.Pending) { Interlocked.CompareExchange(ref m_CallNesting, Nesting.Idle, Nesting.InternalIO); GlobalLog.Print("WriteHeaders() callNesting: " + m_CallNesting.ToString()); } } } else { GlobalLog.Print("ConnectStream#" + ValidationHelper.HashString(this) + "::WriteHeaders() ignoring since ErrorInStream = true"); } if(error != WebExceptionStatus.Pending) { //if error is Pending, and this is async, the request callback will be invoked from the stream callback. m_Request.WriteHeadersCallback(error, this, async); } GlobalLog.Leave("ConnectStream#" + ValidationHelper.HashString(this) + "::WriteHeaders", error.ToString()); } // Wrapper for Connection internal void PollAndRead(bool userRetrievedStream) { m_Connection.PollAndRead(m_Request, userRetrievedStream); } private void SafeSetSocketTimeout(SocketShutdown mode) { if(Eof){ return; } int timeout; if (mode == SocketShutdown.Receive) { timeout = ReadTimeout; } else /*if (mode == SocketShutdown.Send)*/ { timeout = WriteTimeout; } Connection connection = m_Connection; if (connection!=null) { NetworkStream networkStream = connection.NetworkStream; if (networkStream!=null) { networkStream.SetSocketTimeoutOption(mode, timeout, false); } } } /*++ Close - Close the stream Called when the stream is closed. We close our parent stream first. Then if this is a write stream, we'll send the terminating chunk (if needed) and call the connection DoneWriting() method. Input: Nothing. Returns: Nothing. --*/ protected override void Dispose(bool disposing) { #if DEBUG using (GlobalLog.SetThreadKind(ThreadKinds.User | ThreadKinds.Sync)) { #endif try { GlobalLog.Print("ConnectStream#" + ValidationHelper.HashString(this) + "::Close()"); if(Logging.On)Logging.Enter(Logging.Web, this, "Close", ""); ((ICloseEx)this).CloseEx(CloseExState.Normal); if(Logging.On)Logging.Exit(Logging.Web, this, "Close", ""); } finally { base.Dispose(disposing); } #if DEBUG } #endif } internal void CloseInternal(bool internalCall) { GlobalLog.ThreadContract(ThreadKinds.Unknown, "ConnectStream#" + ValidationHelper.HashString(this) + "::Abort"); ((ICloseEx)this).CloseEx((internalCall ? CloseExState.Silent : CloseExState.Normal)); } void ICloseEx.CloseEx(CloseExState closeState) { GlobalLog.ThreadContract(ThreadKinds.Unknown, "ConnectStream#" + ValidationHelper.HashString(this) + "::Abort"); CloseInternal( (closeState & CloseExState.Silent) != 0, (closeState & CloseExState.Abort) != 0 ); GC.SuppressFinalize(this); } // // Optionally sends chunk terminator and proceeds with close that was collided with pending user write IO // void ResumeInternalClose(LazyAsyncResult userResult) { GlobalLog.Print("ConnectStream##" + ValidationHelper.HashString(this) + "::ResumeInternalClose(), userResult:" + userResult); // // write stream. terminate our chunking if needed. // if (WriteChunked && !ErrorInStream && !m_IgnoreSocketErrors) { m_IgnoreSocketErrors = true; try { if (userResult == null) { SafeSetSocketTimeout(SocketShutdown.Send); m_Connection.Write(NclConstants.ChunkTerminator, 0, NclConstants.ChunkTerminator.Length); } else { m_Connection.BeginWrite(NclConstants.ChunkTerminator, 0, NclConstants.ChunkTerminator.Length, new AsyncCallback(ResumeClose_Part2_Wrapper), userResult); return; } } catch (Exception exception) { GlobalLog.Print("ConnectStream#" + ValidationHelper.HashString(this) + "::CloseInternal() exceptionOnWrite:" + exception.Message); } } ResumeClose_Part2(userResult); //never throws } void ResumeClose_Part2_Wrapper(IAsyncResult ar) { try { m_Connection.EndWrite(ar); } catch (Exception exception) { GlobalLog.Print("ConnectStream#" + ValidationHelper.HashString(this) + "::ResumeClose_Part2_Wrapper() ignoring exceptionOnWrite:" + exception.Message); } ResumeClose_Part2((LazyAsyncResult)ar.AsyncState); } private void ResumeClose_Part2(LazyAsyncResult userResult) { try { try { if (ErrorInStream) { GlobalLog.Print("ConnectStream#" + ValidationHelper.HashString(this) + "::ResumeClose_Part2() Aborting the connection"); m_Connection.AbortSocket(true); } } finally { CallDone(); GlobalLog.Leave("ConnectStream#" + ValidationHelper.HashString(this) + "::ResumeClose_Part2", "Done"); } } catch { } finally { if (userResult != null) { userResult.InvokeCallback(); } } } // The number should be reasonalbly large private const int AlreadyAborted = 777777; // private void CloseInternal(bool internalCall, bool aborting) { GlobalLog.Enter("ConnectStream#" + ValidationHelper.HashString(this) + "::CloseInternal", internalCall.ToString()); GlobalLog.ThreadContract(ThreadKinds.Unknown, "ConnectStream#" + ValidationHelper.HashString(this) + "::Abort"); bool normalShutDown = !aborting; Exception exceptionOnWrite = null; // // We have to prevent recursion, because we'll call our parents, close, // which might try to flush data. If we're in an error situation, that // will cause an error on the write, which will cause Close to be called // again, etc. // GlobalLog.Print("ConnectStream#" + ValidationHelper.HashString(this) + "::CloseInternal() m_ShutDown:" + m_ShutDown.ToString() + " m_CallNesting:" + m_CallNesting.ToString() + " m_DoneCalled:" + m_DoneCalled.ToString()); //If this is an abort (aborting == true) of a write stream then we will call request.Abort() //that will call us again. To prevent a recursion here, only one abort is allowed. //However, Abort must still override previous normal close if any. if (aborting) { if (Interlocked.Exchange(ref m_ShutDown, AlreadyAborted) >= AlreadyAborted) { GlobalLog.Leave("ConnectStream#" + ValidationHelper.HashString(this) + "::CloseInternal", "already has been Aborted"); return; } } else { //If m_ShutDown != 0, then this method has been already called before, //Hence disregard this (presumably normal) extra close if (Interlocked.Increment(ref m_ShutDown) > 1) { GlobalLog.Leave("ConnectStream#" + ValidationHelper.HashString(this) + "::CloseInternal", "already has been closed"); return; } } // // Since this should be the last call made, we should be at 0 // If not on the read side then it's an error so we should close the socket // If not on the write side then MAY BE we want this write stream to ignore all // further writes and optionally send chunk terminator. // int nesting = (IsPostStream && internalCall && !IgnoreSocketErrors && !BufferOnly && normalShutDown && !NclUtilities.HasShutdownStarted)? Nesting.Closed: Nesting.InError; if (Interlocked.Exchange(ref m_CallNesting, nesting) == Nesting.IoInProgress) { if (nesting == Nesting.Closed) { GlobalLog.Print("ConnectStream#" + ValidationHelper.HashString(this) + "::CloseInternal() PostStream, Internal call and m_CallNesting==1, defer closing until user write completes"); return; } normalShutDown &= !NclUtilities.HasShutdownStarted; } GlobalLog.Print("Close m_CallNesting: " + m_CallNesting.ToString()); // Questionable: Thsi is to avoid throwing on public Close() when IgnoreSocketErrors==true if (IgnoreSocketErrors && IsPostStream && !internalCall) { m_BytesLeftToWrite = 0; } GlobalLog.Print("ConnectStream#" + ValidationHelper.HashString(this) + "::CloseInternal() normalShutDown:" + normalShutDown.ToString() + " m_CallNesting:" + m_CallNesting.ToString() + " m_DoneCalled:" + m_DoneCalled.ToString()); if (IgnoreSocketErrors || !normalShutDown) { GlobalLog.Print("ConnectStream#" + ValidationHelper.HashString(this) + "::CloseInternal() don't read/write on this, dead connection stream."); } else if (!WriteStream) { // // read stream // GlobalLog.Print("ConnectStream#" + ValidationHelper.HashString(this) + "::CloseInternal() callNesting: " + m_CallNesting.ToString()); GlobalLog.Print("ConnectStream#" + ValidationHelper.HashString(this) + "::CloseInternal() read stream, calling DrainSocket()"); #if DEBUG using (GlobalLog.SetThreadKind(ThreadKinds.Sync)) { #endif normalShutDown = DrainSocket(); #if DEBUG } #endif } else { // // write stream. terminate our chunking if needed. // try { if (!ErrorInStream) { // // if not error already, then... // first handle chunking case // if (WriteChunked) { // // no need to buffer here: // on resubmit, we won't be chunking anyway this will send 5 bytes on the wire // GlobalLog.Print("ConnectStream#" + ValidationHelper.HashString(this) + "::CloseInternal() Chunked, writing ChunkTerminator"); try { // The idea behind is that closed stream must not write anything to the wire // Still if we are chunking, the now buffering and future resubmit is possible if (!m_IgnoreSocketErrors) { m_IgnoreSocketErrors = true; SafeSetSocketTimeout(SocketShutdown.Send); #if DEBUG // Until there is an async version of this, we have to assert Sync privileges here. using (GlobalLog.SetThreadKind(ThreadKinds.Sync)) { #endif m_Connection.Write(NclConstants.ChunkTerminator, 0, NclConstants.ChunkTerminator.Length); #if DEBUG } #endif } } catch { GlobalLog.Print("ConnectStream#" + ValidationHelper.HashString(this) + "::CloseInternal() IGNORE chunk write fault"); } m_BytesLeftToWrite = 0; } else if (BytesLeftToWrite>0) { // // not enough bytes written to client // GlobalLog.Leave("ConnectStream#" + ValidationHelper.HashString(this) + "::CloseInternal() BytesLeftToWrite:" + BytesLeftToWrite.ToString() + " throwing not enough bytes written"); throw new IOException(SR.GetString(SR.net_io_notenoughbyteswritten)); } else if (BufferOnly) { // // now we need to use the saved reference to the request the client // closed the write stream. we need to wake up the request, so that it // sends the headers and kick off resubmitting of buffered entity body // GlobalLog.Assert(m_Request != null, "ConnectStream#{0}::CloseInternal|m_Request == null", ValidationHelper.HashString(this)); m_BytesLeftToWrite = BufferedData.Length; m_Request.SwitchToContentLength(); // // writing the headers will kick off the whole request submission process // (including waiting for the 100 Continue and writing the whole entity body) // SafeSetSocketTimeout(SocketShutdown.Send); m_Request.NeedEndSubmitRequest(); GlobalLog.Leave("ConnectStream#" + ValidationHelper.HashString(this) + "::CloseInternal", "Done"); return; } } else { normalShutDown = false; } } catch (Exception exception) { normalShutDown = false; if (NclUtilities.IsFatal(exception)) { m_ErrorException = exception; throw; } exceptionOnWrite = new WebException( NetRes.GetWebStatusString("net_requestaborted", WebExceptionStatus.RequestCanceled), exception, WebExceptionStatus.RequestCanceled, null); GlobalLog.Print("ConnectStream#" + ValidationHelper.HashString(this) + "::CloseInternal() exceptionOnWrite:" + exceptionOnWrite.Message); } } GlobalLog.Print("ConnectStream#" + ValidationHelper.HashString(this) + "::CloseInternal() normalShutDown:" + normalShutDown.ToString() + " m_CallNesting:" + m_CallNesting.ToString() + " m_DoneCalled:" + m_DoneCalled.ToString()); if (!normalShutDown && m_DoneCalled==0) { // If a normal Close (aborting == false) has turned into Abort _inside_ this method, // then check if another abort has been charged from other thread if (!aborting && Interlocked.Exchange(ref m_ShutDown, AlreadyAborted) >= AlreadyAborted){ GlobalLog.Leave("ConnectStream#" + ValidationHelper.HashString(this) + "::CloseInternal", "other thread has charged Abort(), canceling that one"); return; } // // then abort the connection if we finished in error // note: if m_DoneCalled != 0, then we no longer have // control of the socket, so closing would cause us // to close someone else's socket/connection. // m_ErrorException = new WebException( NetRes.GetWebStatusString("net_requestaborted", WebExceptionStatus.RequestCanceled), WebExceptionStatus.RequestCanceled); GlobalLog.Print("ConnectStream#" + ValidationHelper.HashString(this) + "::CloseInternal() Aborting the connection"); m_Connection.AbortSocket(true); // For write stream Abort() we depend on either of two, i.e: // 1. The connection BeginRead is curently posted (means there are no response headers received yet) // 2. The response (read) stream must be closed as well if aborted this (write) stream. // Next block takes care of (2) since otherwise, (1) is true. if (WriteStream) { HttpWebRequest req = m_Request; if (req != null) { req.Abort(); } } if (exceptionOnWrite != null) { GlobalLog.Print("ConnectStream#" + ValidationHelper.HashString(this) + "::CloseInternal() calling CallDone() on exceptionOnWrite:" + exceptionOnWrite.Message); CallDone(); if (!internalCall) { GlobalLog.LeaveException("ConnectStream#" + ValidationHelper.HashString(this) + "::CloseInternal() throwing:", exceptionOnWrite); throw exceptionOnWrite; } } } // // Let the connection know we're done writing or reading. // GlobalLog.Print("ConnectStream#" + ValidationHelper.HashString(this) + "::CloseInternal() calling CallDone()"); CallDone(); GlobalLog.Leave("ConnectStream#" + ValidationHelper.HashString(this) + "::CloseInternal", "Done"); } /*++ Flush - Flush the stream Called when the user wants to flush the stream. This is meaningless to us, so we just ignore it. Input: Nothing. Returns: Nothing. --*/ public override void Flush() { } /*++ Seek - Seek on the stream Called when the user wants to seek the stream. Since we don't support seek, we'll throw an exception. Input: offset - offset to see origin - where to start seeking Returns: Throws exception --*/ public override long Seek(long offset, SeekOrigin origin) { throw new NotSupportedException(SR.GetString(SR.net_noseek)); } /*++ SetLength - Set the length on the stream Called when the user wants to set the stream length. Since we don't support seek, we'll throw an exception. Input: value - length of stream to set Returns: Throws exception --*/ public override void SetLength(long value) { throw new NotSupportedException(SR.GetString(SR.net_noseek)); } /*++ DrainSocket - Reads data from the connection, till we'll ready to finish off the stream, or close the connection for good. returns - bool true on success, false on failure --*/ private bool DrainSocket() { GlobalLog.Enter("ConnectStream::DrainSocket"); GlobalLog.ThreadContract(ThreadKinds.Unknown, "ConnectStream#" + ValidationHelper.HashString(this) + "::DrainSocket"); if (IgnoreSocketErrors) { GlobalLog.Leave("ConnectStream#" + ValidationHelper.HashString(this) + "::DrainSocket() IgnoreSocketErrors == true, stream is dead.", true); return true; } // // If its not chunked and we have a read buffer, don't waste time coping the data // around againg, just pretend its gone, i.exception. make it die // long ReadBytes = m_ReadBytes; if (!m_Chunked) { GlobalLog.Print("ConnectStream#" + ValidationHelper.HashString(this) + "::DrainSocket() m_ReadBytes:" + m_ReadBytes.ToString() + " m_ReadBufferSize:" + m_ReadBufferSize.ToString()); if (m_ReadBufferSize != 0) { // // There's stuff in our read buffer. // Update our internal read buffer state with what we took. // m_ReadOffset += m_ReadBufferSize; GlobalLog.Print("ConnectStream#" + ValidationHelper.HashString(this) + "::DrainSocket() m_ReadBytes:" + m_ReadBytes.ToString() + " m_ReadOffset:" + m_ReadOffset.ToString()); if (m_ReadBytes != -1) { m_ReadBytes -= m_ReadBufferSize; GlobalLog.Print("m_ReadBytes = "+m_ReadBytes); // error handling, we shouldn't hang here if trying to drain, and there // is a mismatch with Content-Length and actual bytes. // // Note: I've seen this often happen with some sites where they return 204 // in violation of HTTP/1.1 with a Content-Length > 0 if (m_ReadBytes < 0) { GlobalLog.Print("ConnectStream#" + ValidationHelper.HashString(this) + "::DrainSocket() m_ReadBytes:" + m_ReadBytes.ToString() + " incorrect Content-Length? setting m_ReadBytes to 0 and returning false."); m_ReadBytes = 0; GlobalLog.Leave("ConnectStream::DrainSocket", false); return false; } } m_ReadBufferSize = 0; // If the read buffer size has gone to 0, null out our pointer // to it so maybe it'll be garbage-collected faster. m_ReadBuffer = null; } // exit out of drain Socket when there is no connection-length, // it doesn't make sense to drain a possible empty socket, // when we're just going to close it. if (ReadBytes == -1) { GlobalLog.Leave("ConnectStream#" + ValidationHelper.HashString(this) + "::DrainSocket() ReadBytes==-1, returning true"); return true; } } // // in error or Eof, we may be in a weird state // so we need return if we as if we don't have any more // space to read, note Eof is true when there is an error // if (this.Eof) { GlobalLog.Leave("ConnectStream#" + ValidationHelper.HashString(this) + "::DrainSocket() Eof, returning true"); return true; } // // If we're draining more than 64K, then we should // just close the socket, since it would be costly to // do this. // if (m_ReadBytes > c_MaxDrainBytes) { GlobalLog.Print("ConnectStream#" + ValidationHelper.HashString(this) + "::DrainSocket() m_ReadBytes:" + m_ReadBytes.ToString() + " too large, Closing the Connection"); m_Connection.AbortSocket(false); GlobalLog.Leave("ConnectStream::DrainSocket", true); return true; } // // Now drain the socket the old, slow way by reading or pasing Chunked stuff // m_Draining = true; int bytesRead; for (;;) { try { bytesRead = ReadWithoutValidation(s_DrainingBuffer, 0, s_DrainingBuffer.Length, false); GlobalLog.Print("ConnectStream#" + ValidationHelper.HashString(this) + "::DrainSocket() drained bytesRead:" + bytesRead.ToString() + " bytes"); if (bytesRead<=0) { break; } } catch (Exception exception) { if (NclUtilities.IsFatal(exception)) throw; GlobalLog.Print("exception" + exception.ToString()); bytesRead = -1; break; } } GlobalLog.Leave("ConnectStream::DrainSocket", true); return bytesRead > 0; } internal static byte[] s_DrainingBuffer = new byte[4096]; /*++ IOError - Handle an IOError on the stream. Input: exception - optional Exception that will be later thrown Returns: Nothing or may throw --*/ private void IOError(Exception exception) { IOError(exception, true); } // If willThrow=true, it means that the caller intends to throw an // exception. If there is already an earlier exception, then IO error // will throw that one instead. Otherwise IOError() will let the caller // throw the exception (typically the one passed in) // If willThrow = false, the user does not want an exception to be // thrown. So IOError should not throw an exception. private void IOError(Exception exception, bool willThrow) { GlobalLog.Enter("ConnectStream#" + ValidationHelper.HashString(this) + "::IOError", "Connection# " + ValidationHelper.HashString(m_Connection)); GlobalLog.ThreadContract(ThreadKinds.Unknown, "ConnectStream#" + ValidationHelper.HashString(this) + "::IOError"); string Msg; if (m_ErrorException == null) { if ( exception == null ) { if ( !WriteStream ) { Msg = SR.GetString(SR.net_io_readfailure, SR.GetString(SR.net_io_connectionclosed)); } else { Msg = SR.GetString(SR.net_io_writefailure, SR.GetString(SR.net_io_connectionclosed)); } Interlocked.CompareExchange (ref m_ErrorException, new IOException(Msg), null); } else { willThrow &= Interlocked.CompareExchange (ref m_ErrorException, exception, null) != null; } } // Leaving this line as is, does not hurt but consider removing for clarity. m_ChunkEofRecvd = true; ConnectionReturnResult returnResult = null; if (WriteStream) m_Connection.HandleConnectStreamException(true, false, WebExceptionStatus.SendFailure, ref returnResult, m_ErrorException); else m_Connection.HandleConnectStreamException(false, true, WebExceptionStatus.ReceiveFailure, ref returnResult, m_ErrorException); CallDone(returnResult); GlobalLog.Leave("ConnectStream#" + ValidationHelper.HashString(this) + "::IOError"); if (willThrow) { throw m_ErrorException; } } /*++ GetChunkHeader A private utility routine to convert an integer to a chunk header, which is an ASCII hex number followed by a CRLF. The header is retuned as a byte array. Input: size - Chunk size to be encoded offset - Out parameter where we store offset into buffer. Returns: A byte array with the header in int. --*/ internal static byte[] GetChunkHeader(int size, out int offset) { GlobalLog.Enter("ConnectStream::GetChunkHeader", "size:" + size.ToString()); uint Mask = 0xf0000000; byte[] Header = new byte[10]; int i; offset = -1; // // Loop through the size, looking at each nibble. If it's not 0 // convert it to hex. Save the index of the first non-zero // byte. // for (i = 0; i < 8; i++, size <<= 4) { // // offset == -1 means that we haven't found a non-zero nibble // yet. If we haven't found one, and the current one is zero, // don't do anything. // if (offset == -1) { if ((size & Mask) == 0) { continue; } } // // Either we have a non-zero nibble or we're no longer skipping // leading zeros. Convert this nibble to ASCII and save it. // uint Temp = (uint)size >> 28; if (Temp < 10) { Header[i] = (byte)(Temp + '0'); } else { Header[i] = (byte)((Temp - 10) + 'A'); } // // If we haven't found a non-zero nibble yet, we've found one // now, so remember that. // if (offset == -1) { offset = i; } } Header[8] = (byte)'\r'; Header[9] = (byte)'\n'; GlobalLog.Leave("ConnectStream::GetChunkHeader"); return Header; } } // // Base Memory stream does not overide BeginXXX and that will cause base Stream // to do async delegates and that is not thread safe on async Stream.Close() // // This class will always complete async requests synchronously // internal sealed class SyncMemoryStream: MemoryStream { private int m_ReadTimeout; private int m_WriteTimeout; internal SyncMemoryStream(byte[] bytes): base(bytes, false) { m_ReadTimeout = m_WriteTimeout = System.Threading.Timeout.Infinite; } // internal SyncMemoryStream(int initialCapacity): base(initialCapacity) { m_ReadTimeout = m_WriteTimeout = System.Threading.Timeout.Infinite; } public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback callback, Object state) { int result = Read(buffer, offset, count); return new LazyAsyncResult(null, state, callback, result); } // public override int EndRead(IAsyncResult asyncResult) { LazyAsyncResult lazyResult = (LazyAsyncResult)asyncResult; return (int) lazyResult.InternalWaitForCompletion(); } public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback callback, Object state) { Write(buffer, offset, count); return new LazyAsyncResult(null, state, callback, null); } // public override void EndWrite(IAsyncResult asyncResult) { LazyAsyncResult lazyResult = (LazyAsyncResult)asyncResult; lazyResult.InternalWaitForCompletion(); } // public override bool CanTimeout { get { return true; } } public override int ReadTimeout { get { return m_ReadTimeout; } set { m_ReadTimeout = value; } } public override int WriteTimeout { get { return m_WriteTimeout; } set { m_WriteTimeout = value; } } } }
Link Menu
This book is available now!
Buy at Amazon US or
Buy at Amazon UK
- SqlBinder.cs
- TraceFilter.cs
- TransformPatternIdentifiers.cs
- ControlIdConverter.cs
- LOSFormatter.cs
- ControlCollection.cs
- Rect3DValueSerializer.cs
- AsyncDataRequest.cs
- TextBoxBase.cs
- UnescapedXmlDiagnosticData.cs
- CodeEntryPointMethod.cs
- InfiniteIntConverter.cs
- CultureData.cs
- CfgParser.cs
- HtmlFormWrapper.cs
- SecurityKeyIdentifier.cs
- COAUTHIDENTITY.cs
- LineGeometry.cs
- CreateUserWizard.cs
- SecurityException.cs
- TextElementEnumerator.cs
- DataSetUtil.cs
- FormsIdentity.cs
- RequestSecurityTokenForGetBrowserToken.cs
- DataGridCell.cs
- TextTreeRootNode.cs
- SettingsAttributes.cs
- ConfigurationException.cs
- StylusPointPropertyUnit.cs
- AdRotator.cs
- BamlVersionHeader.cs
- AVElementHelper.cs
- SmiGettersStream.cs
- EditorPartCollection.cs
- StreamUpdate.cs
- MediaSystem.cs
- HandleScope.cs
- DataTableReader.cs
- DataGridTablesFactory.cs
- Cursor.cs
- XmlDataSourceNodeDescriptor.cs
- SoapClientProtocol.cs
- PopupRoot.cs
- NotFiniteNumberException.cs
- RoutedEventValueSerializer.cs
- Path.cs
- PointIndependentAnimationStorage.cs
- XamlSerializerUtil.cs
- DataGridViewAccessibleObject.cs
- Evidence.cs
- EditorBrowsableAttribute.cs
- SoapSchemaMember.cs
- XsdValidatingReader.cs
- SelectionChangedEventArgs.cs
- FixUp.cs
- DocumentGridPage.cs
- DefaultValueTypeConverter.cs
- TextTreeDeleteContentUndoUnit.cs
- GradientStopCollection.cs
- ByteAnimationUsingKeyFrames.cs
- XmlWriterTraceListener.cs
- DataTransferEventArgs.cs
- IdleTimeoutMonitor.cs
- XsltException.cs
- DetailsViewUpdateEventArgs.cs
- RSAProtectedConfigurationProvider.cs
- WebPartConnection.cs
- EllipticalNodeOperations.cs
- BamlWriter.cs
- SoapMessage.cs
- PassportPrincipal.cs
- DragDeltaEventArgs.cs
- ExpressionBuilderCollection.cs
- SecUtil.cs
- XmlEntity.cs
- ItemDragEvent.cs
- SkewTransform.cs
- MultiTargetingUtil.cs
- StructuredTypeEmitter.cs
- wmiprovider.cs
- PropertyExpression.cs
- PropertyDescriptorCollection.cs
- ConsumerConnectionPointCollection.cs
- DesignerLinkAdapter.cs
- AssemblyInfo.cs
- XsltOutput.cs
- SafeReversePInvokeHandle.cs
- Exceptions.cs
- Cell.cs
- ImageSource.cs
- CalendarDay.cs
- EntityDataSourceViewSchema.cs
- HandleCollector.cs
- UIntPtr.cs
- DataGridViewComboBoxEditingControl.cs
- XmlDeclaration.cs
- MultiPartWriter.cs
- CompensationTokenData.cs
- entityreference_tresulttype.cs
- ActiveXContainer.cs