// Accepts an incoming binary security blob and returns an outgoing binary security blob. internal byte[] GetOutgoingBlob(byte[] incomingBlob, bool throwOnError, out SecurityStatusPal statusCode) { if (NetEventSource.IsEnabled) { NetEventSource.Enter(this, incomingBlob); } var list = new List <SecurityBuffer>(2); if (incomingBlob != null) { list.Add(new SecurityBuffer(incomingBlob, SecurityBufferType.SECBUFFER_TOKEN)); } if (_channelBinding != null) { list.Add(new SecurityBuffer(_channelBinding)); } SecurityBuffer[] inSecurityBufferArray = null; if (list.Count > 0) { inSecurityBufferArray = list.ToArray(); } var outSecurityBuffer = new SecurityBuffer(_tokenSize, SecurityBufferType.SECBUFFER_TOKEN); bool firstTime = _securityContext == null; try { if (!_isServer) { // client session statusCode = NegotiateStreamPal.InitializeSecurityContext( _credentialsHandle, ref _securityContext, _spn, _requestedContextFlags, inSecurityBufferArray, outSecurityBuffer, ref _contextFlags); if (NetEventSource.IsEnabled) { NetEventSource.Info(this, $"SSPIWrapper.InitializeSecurityContext() returns statusCode:0x{((int)statusCode.ErrorCode):x8} ({statusCode})"); } if (statusCode.ErrorCode == SecurityStatusPalErrorCode.CompleteNeeded) { var inSecurityBuffers = new SecurityBuffer[1]; inSecurityBuffers[0] = outSecurityBuffer; statusCode = NegotiateStreamPal.CompleteAuthToken(ref _securityContext, inSecurityBuffers); if (NetEventSource.IsEnabled) { NetEventSource.Info(this, $"SSPIWrapper.CompleteAuthToken() returns statusCode:0x{((int)statusCode.ErrorCode):x8} ({statusCode})"); } outSecurityBuffer.token = null; } } else { // Server session. statusCode = NegotiateStreamPal.AcceptSecurityContext( _credentialsHandle, ref _securityContext, _requestedContextFlags, inSecurityBufferArray, outSecurityBuffer, ref _contextFlags); if (NetEventSource.IsEnabled) { NetEventSource.Info(this, $"SSPIWrapper.AcceptSecurityContext() returns statusCode:0x{((int)statusCode.ErrorCode):x8} ({statusCode})"); } } } finally { // // Assuming the ISC or ASC has referenced the credential on the first successful call, // we want to decrement the effective ref count by "disposing" it. // The real dispose will happen when the security context is closed. // Note if the first call was not successful the handle is physically destroyed here. // if (firstTime && _credentialsHandle != null) { _credentialsHandle.Dispose(); } } if (NegoState.IsError(statusCode)) { CloseContext(); _isCompleted = true; if (throwOnError) { Exception exception = NegotiateStreamPal.CreateExceptionFromError(statusCode); if (NetEventSource.IsEnabled) { NetEventSource.Exit(this, exception); } throw exception; } if (NetEventSource.IsEnabled) { NetEventSource.Exit(this, $"null statusCode:0x{((int)statusCode.ErrorCode):x8} ({statusCode})"); } return(null); } else if (firstTime && _credentialsHandle != null) { // Cache until it is pushed out by newly incoming handles. SSPIHandleCache.CacheCredential(_credentialsHandle); } // The return value will tell us correctly if the handshake is over or not if (statusCode.ErrorCode == SecurityStatusPalErrorCode.OK) { // Success. _isCompleted = true; } else if (NetEventSource.IsEnabled) { // We need to continue. if (NetEventSource.IsEnabled) { NetEventSource.Info(this, $"need continue statusCode:0x{((int)statusCode.ErrorCode):x8} ({statusCode}) _securityContext:{_securityContext}"); } } if (NetEventSource.IsEnabled) { if (NetEventSource.IsEnabled) { NetEventSource.Exit(this, $"IsCompleted: {IsCompleted}"); } } return(outSecurityBuffer.token); }
internal byte[]? GetOutgoingBlob(ReadOnlySpan <byte> incomingBlob, bool throwOnError, out SecurityStatusPal statusCode) { _tokenBuffer ??= _tokenSize == 0 ? Array.Empty <byte>() : new byte[_tokenSize]; bool firstTime = _securityContext == null; int resultBlobLength; try { if (!_isServer) { // client session statusCode = NegotiateStreamPal.InitializeSecurityContext( ref _credentialsHandle !, ref _securityContext, _spn, _requestedContextFlags, incomingBlob, _channelBinding, ref _tokenBuffer, out resultBlobLength, ref _contextFlags); if (NetEventSource.Log.IsEnabled()) { NetEventSource.Info(this, $"SSPIWrapper.InitializeSecurityContext() returns statusCode:0x{((int)statusCode.ErrorCode):x8} ({statusCode})"); } if (statusCode.ErrorCode == SecurityStatusPalErrorCode.CompleteNeeded) { statusCode = NegotiateStreamPal.CompleteAuthToken(ref _securityContext, _tokenBuffer.AsSpan(0, resultBlobLength)); if (NetEventSource.Log.IsEnabled()) { NetEventSource.Info(this, $"SSPIWrapper.CompleteAuthToken() returns statusCode:0x{((int)statusCode.ErrorCode):x8} ({statusCode})"); } resultBlobLength = 0; } } else { // Server session. statusCode = NegotiateStreamPal.AcceptSecurityContext( _credentialsHandle, ref _securityContext, _requestedContextFlags, incomingBlob, _channelBinding, ref _tokenBuffer, out resultBlobLength, ref _contextFlags); if (NetEventSource.Log.IsEnabled()) { NetEventSource.Info(this, $"SSPIWrapper.AcceptSecurityContext() returns statusCode:0x{((int)statusCode.ErrorCode):x8} ({statusCode})"); } } } finally { // // Assuming the ISC or ASC has referenced the credential on the first successful call, // we want to decrement the effective ref count by "disposing" it. // The real dispose will happen when the security context is closed. // Note if the first call was not successful the handle is physically destroyed here. // if (firstTime) { _credentialsHandle?.Dispose(); } } if (((int)statusCode.ErrorCode >= (int)SecurityStatusPalErrorCode.OutOfMemory)) { CloseContext(); _isCompleted = true; _tokenBuffer = null; if (throwOnError) { throw NegotiateStreamPal.CreateExceptionFromError(statusCode); } return(null); } else if (firstTime && _credentialsHandle != null) { // Cache until it is pushed out by newly incoming handles. SSPIHandleCache.CacheCredential(_credentialsHandle); } byte[]? result = resultBlobLength == 0 || _tokenBuffer == null ? null : _tokenBuffer.Length == resultBlobLength ? _tokenBuffer : _tokenBuffer[0..resultBlobLength];
private int ReadCore(byte[] buffer, int offset, int size) { uint dataRead = 0; if (_dataChunkIndex != -1) { dataRead = Interop.HttpApi.GetChunks(_httpContext.Request.RequestBuffer, _httpContext.Request.OriginalBlobAddress, ref _dataChunkIndex, ref _dataChunkOffset, buffer, offset, size); } if (_dataChunkIndex == -1 && dataRead < size) { if (NetEventSource.Log.IsEnabled()) { NetEventSource.Info(this, "size:" + size + " offset:" + offset); } uint statusCode = 0; uint extraDataRead = 0; offset += (int)dataRead; size -= (int)dataRead; //the http.sys team recommends that we limit the size to 128kb if (size > MaxReadSize) { size = MaxReadSize; } fixed(byte *pBuffer = buffer) { // issue unmanaged blocking call if (NetEventSource.Log.IsEnabled()) { NetEventSource.Info(this, "Calling Interop.HttpApi.HttpReceiveRequestEntityBody"); } uint flags = 0; if (!_inOpaqueMode) { flags = (uint)Interop.HttpApi.HTTP_FLAGS.HTTP_RECEIVE_REQUEST_FLAG_COPY_BODY; } statusCode = Interop.HttpApi.HttpReceiveRequestEntityBody( _httpContext.RequestQueueHandle, _httpContext.RequestId, flags, (void *)(pBuffer + offset), (uint)size, out extraDataRead, null); dataRead += extraDataRead; if (NetEventSource.Log.IsEnabled()) { NetEventSource.Info(this, "Call to Interop.HttpApi.HttpReceiveRequestEntityBody returned:" + statusCode + " dataRead:" + dataRead); } } if (statusCode != Interop.HttpApi.ERROR_SUCCESS && statusCode != Interop.HttpApi.ERROR_HANDLE_EOF) { Exception exception = new HttpListenerException((int)statusCode); if (NetEventSource.Log.IsEnabled()) { NetEventSource.Error(this, exception.ToString()); } throw exception; } UpdateAfterRead(statusCode, dataRead); } if (NetEventSource.Log.IsEnabled()) { NetEventSource.DumpBuffer(this, buffer, offset, (int)dataRead); NetEventSource.Info(this, "returning dataRead:" + dataRead); NetEventSource.Exit(this, "dataRead:" + dataRead); } return((int)dataRead); }
/// <summary> /// <para>Thread for the timer. Ignores all exceptions. If no activity occurs for a while, /// the thread will shut down.</para> /// </summary> private static void ThreadProc() { if (NetEventSource.IsEnabled) { NetEventSource.Enter(null); } #if DEBUG DebugThreadTracking.SetThreadSource(ThreadKinds.Timer); using (DebugThreadTracking.SetThreadKind(ThreadKinds.System | ThreadKinds.Async)) { #endif // Set this thread as a background thread. On AppDomain/Process shutdown, the thread will just be killed. Thread.CurrentThread.IsBackground = true; // Keep a permanent lock on s_Queues. This lets for example Shutdown() know when this thread isn't running. lock (s_queues) { // If shutdown was recently called, abort here. if (Interlocked.CompareExchange(ref s_threadState, (int)TimerThreadState.Running, (int)TimerThreadState.Running) != (int)TimerThreadState.Running) { return; } bool running = true; while (running) { try { s_threadReadyEvent.Reset(); while (true) { // Copy all the new queues to the real queues. Since only this thread modifies the real queues, it doesn't have to lock it. if (s_newQueues.Count > 0) { lock (s_newQueues) { for (LinkedListNode <WeakReference> node = s_newQueues.First; node != null; node = s_newQueues.First) { s_newQueues.Remove(node); s_queues.AddLast(node); } } } int now = Environment.TickCount; int nextTick = 0; bool haveNextTick = false; for (LinkedListNode <WeakReference> node = s_queues.First; node != null; /* node = node.Next must be done in the body */) { TimerQueue queue = (TimerQueue)node.Value.Target; if (queue == null) { LinkedListNode <WeakReference> next = node.Next; s_queues.Remove(node); node = next; continue; } // Fire() will always return values that should be interpreted as later than 'now' (that is, even if 'now' is // returned, it is 0x100000000 milliseconds in the future). There's also a chance that Fire() will return a value // intended as > 0x100000000 milliseconds from 'now'. Either case will just cause an extra scan through the timers. int nextTickInstance; if (queue.Fire(out nextTickInstance) && (!haveNextTick || IsTickBetween(now, nextTick, nextTickInstance))) { nextTick = nextTickInstance; haveNextTick = true; } node = node.Next; } // Figure out how long to wait, taking into account how long the loop took. // Add 15 ms to compensate for poor TickCount resolution (want to guarantee a firing). int newNow = Environment.TickCount; int waitDuration = haveNextTick ? (int)(IsTickBetween(now, nextTick, newNow) ? Math.Min(unchecked ((uint)(nextTick - newNow)), (uint)(Int32.MaxValue - TickCountResolution)) + TickCountResolution : 0) : ThreadIdleTimeoutMilliseconds; if (NetEventSource.IsEnabled) { NetEventSource.Info(null, $"Waiting for {waitDuration}ms"); } int waitResult = WaitHandle.WaitAny(s_threadEvents, waitDuration, false); // 0 is s_ThreadShutdownEvent - die. if (waitResult == 0) { if (NetEventSource.IsEnabled) { NetEventSource.Info(null, "Awoke, cause: Shutdown"); } running = false; break; } if (NetEventSource.IsEnabled) { NetEventSource.Info(null, $"Awoke, cause {(waitResult == WaitHandle.WaitTimeout ? "Timeout" : "Prod")}"); } // If we timed out with nothing to do, shut down. if (waitResult == WaitHandle.WaitTimeout && !haveNextTick) { Interlocked.CompareExchange(ref s_threadState, (int)TimerThreadState.Idle, (int)TimerThreadState.Running); // There could have been one more prod between the wait and the exchange. Check, and abort if necessary. if (s_threadReadyEvent.WaitOne(0, false)) { if (Interlocked.CompareExchange(ref s_threadState, (int)TimerThreadState.Running, (int)TimerThreadState.Idle) == (int)TimerThreadState.Idle) { continue; } } running = false; break; } } } catch (Exception exception) { if (ExceptionCheck.IsFatal(exception)) { throw; } if (NetEventSource.IsEnabled) { NetEventSource.Error(null, exception); } // The only options are to continue processing and likely enter an error-loop, // shut down timers for this AppDomain, or shut down the AppDomain. Go with shutting // down the AppDomain in debug, and going into a loop in retail, but try to make the // loop somewhat slow. Note that in retail, this can only be triggered by OutOfMemory or StackOverflow, // or an exception thrown within TimerThread - the rest are caught in Fire(). #if !DEBUG Thread.Sleep(1000); #else throw; #endif } } } if (NetEventSource.IsEnabled) { NetEventSource.Info(null, "Stop"); } #if DEBUG } #endif }
internal Interop.HttpApi.HTTP_FLAGS ComputeHeaders() { Interop.HttpApi.HTTP_FLAGS flags = Interop.HttpApi.HTTP_FLAGS.NONE; if (NetEventSource.IsEnabled) { NetEventSource.Info(this); } Debug.Assert(!ComputedHeaders, "ComputedHeaders is true."); _responseState = ResponseState.ComputedHeaders; ComputeCoreHeaders(); if (NetEventSource.IsEnabled) { NetEventSource.Info(this, $"flags: {flags} _boundaryType: {_boundaryType} _contentLength: {_contentLength} _keepAlive: {_keepAlive}"); } if (_boundaryType == BoundaryType.None) { if (HttpListenerRequest.ProtocolVersion.Minor == 0) { _keepAlive = false; } else { _boundaryType = BoundaryType.Chunked; } if (CanSendResponseBody(_httpContext.Response.StatusCode)) { _contentLength = -1; } else { ContentLength64 = 0; } } if (NetEventSource.IsEnabled) { NetEventSource.Info(this, $"flags:{flags} _BoundaryType:{_boundaryType} _contentLength:{_contentLength} _keepAlive: {_keepAlive}"); } if (_boundaryType == BoundaryType.ContentLength) { Headers[HttpResponseHeader.ContentLength] = _contentLength.ToString("D", NumberFormatInfo.InvariantInfo); if (_contentLength == 0) { flags = Interop.HttpApi.HTTP_FLAGS.NONE; } } else if (_boundaryType == BoundaryType.Chunked) { Headers[HttpResponseHeader.TransferEncoding] = "chunked"; } else if (_boundaryType == BoundaryType.None) { flags = Interop.HttpApi.HTTP_FLAGS.NONE; // seems like HTTP_SEND_RESPONSE_FLAG_MORE_DATA but this hangs the app; } else { _keepAlive = false; } if (!_keepAlive) { Headers.Add(HttpResponseHeader.Connection, "close"); if (flags == Interop.HttpApi.HTTP_FLAGS.NONE) { flags = Interop.HttpApi.HTTP_FLAGS.HTTP_SEND_RESPONSE_FLAG_DISCONNECT; } } else { if (HttpListenerRequest.ProtocolVersion.Minor == 0) { Headers[HttpResponseHeader.KeepAlive] = "true"; } } if (NetEventSource.IsEnabled) { NetEventSource.Info(this, $"flags:{flags} _BoundaryType:{_boundaryType} _contentLength:{_contentLength} _keepAlive: {_keepAlive}"); } return(flags); }
private void DisposeCore() { Interop.HttpApi.HTTP_FLAGS flags = ComputeLeftToWrite(); if (_leftToWrite > 0 && !_inOpaqueMode) { throw new InvalidOperationException(SR.net_io_notenoughbyteswritten); } bool sentHeaders = _httpContext.Response.SentHeaders; if (sentHeaders && _leftToWrite == 0) { if (NetEventSource.IsEnabled) { NetEventSource.Exit(this); } return; } uint statusCode = 0; if ((_httpContext.Response.BoundaryType == BoundaryType.Chunked || _httpContext.Response.BoundaryType == BoundaryType.None) && !string.Equals(_httpContext.Request.HttpMethod, "HEAD", StringComparison.OrdinalIgnoreCase)) { if (_httpContext.Response.BoundaryType == BoundaryType.None) { flags |= Interop.HttpApi.HTTP_FLAGS.HTTP_SEND_RESPONSE_FLAG_DISCONNECT; } fixed(void *pBuffer = &s_chunkTerminator[0]) { Interop.HttpApi.HTTP_DATA_CHUNK *pDataChunk = null; if (_httpContext.Response.BoundaryType == BoundaryType.Chunked) { Interop.HttpApi.HTTP_DATA_CHUNK dataChunk = default; dataChunk.DataChunkType = Interop.HttpApi.HTTP_DATA_CHUNK_TYPE.HttpDataChunkFromMemory; dataChunk.pBuffer = (byte *)pBuffer; dataChunk.BufferLength = (uint)s_chunkTerminator.Length; pDataChunk = &dataChunk; } if (!sentHeaders) { statusCode = _httpContext.Response.SendHeaders(pDataChunk, null, flags, false); } else { if (NetEventSource.IsEnabled) { NetEventSource.Info(this, "Calling Interop.HttpApi.HttpSendResponseEntityBody"); } statusCode = Interop.HttpApi.HttpSendResponseEntityBody( _httpContext.RequestQueueHandle, _httpContext.RequestId, (uint)flags, pDataChunk != null ? (ushort)1 : (ushort)0, pDataChunk, null, SafeLocalAllocHandle.Zero, 0, null, null); if (NetEventSource.IsEnabled) { NetEventSource.Info(this, "Call to Interop.HttpApi.HttpSendResponseEntityBody returned:" + statusCode); } if (_httpContext.Listener.IgnoreWriteExceptions) { if (NetEventSource.IsEnabled) { NetEventSource.Info(this, "Suppressing error"); } statusCode = Interop.HttpApi.ERROR_SUCCESS; } } } } else { if (!sentHeaders) { statusCode = _httpContext.Response.SendHeaders(null, null, flags, false); } } if (statusCode != Interop.HttpApi.ERROR_SUCCESS && statusCode != Interop.HttpApi.ERROR_HANDLE_EOF) { Exception exception = new HttpListenerException((int)statusCode); if (NetEventSource.IsEnabled) { NetEventSource.Error(this, exception.ToString()); } _httpContext.Abort(); throw exception; } _leftToWrite = 0; }
/// Pipelined command resolution. /// How this works: /// A list of commands that need to be sent to the FTP server are spliced together into an array, /// each command such STOR, PORT, etc, is sent to the server, then the response is parsed into a string, /// with the response, the delegate is called, which returns an instruction (either continue, stop, or read additional /// responses from server). protected Stream?ContinueCommandPipeline() { // In async case, The BeginWrite can actually result in a // series of synchronous completions that eventually close // the connection. So we need to save the members that // we need to access, since they may not be valid after // BeginWrite returns bool isAsync = _isAsync; while (_index < _commands !.Length) { if (_doSend) { if (_index < 0) { throw new InternalException(); } byte[] sendBuffer = Encoding.GetBytes(_commands[_index].Command); if (NetEventSource.Log.IsEnabled()) { string sendCommand = _commands[_index].Command.Substring(0, _commands[_index].Command.Length - 2); if (_commands[_index].HasFlag(PipelineEntryFlags.DontLogParameter)) { int index = sendCommand.IndexOf(' '); if (index != -1) { sendCommand = string.Concat(sendCommand.AsSpan(0, index), " ********"); } } if (NetEventSource.Log.IsEnabled()) { NetEventSource.Info(this, $"Sending command {sendCommand}"); } } try { if (isAsync) { BeginWrite(sendBuffer, 0, sendBuffer.Length, s_writeCallbackDelegate, this); } else { Write(sendBuffer, 0, sendBuffer.Length); } } catch (IOException) { MarkAsRecoverableFailure(); throw; } catch { throw; } if (isAsync) { return(null); } } Stream?stream = null; bool isReturn = PostSendCommandProcessing(ref stream); if (isReturn) { return(stream); } } lock (this) { Close(); } return(null); }
internal HttpResponseStreamAsyncResult(object asyncObject, object?userState, AsyncCallback?callback, byte[] buffer, int offset, int size, bool chunked, bool sentHeaders, ThreadPoolBoundHandle boundHandle) : base(asyncObject, userState, callback) { _boundHandle = boundHandle; _sentHeaders = sentHeaders; if (size == 0) { _dataChunks = null; _pOverlapped = boundHandle.AllocateNativeOverlapped(s_IOCallback, state: this, pinData: null); } else { _dataChunks = new Interop.HttpApi.HTTP_DATA_CHUNK[chunked ? 3 : 1]; if (NetEventSource.Log.IsEnabled()) { NetEventSource.Info(this, "m_pOverlapped:0x" + ((IntPtr)_pOverlapped).ToString("x8")); } object[] objectsToPin = new object[1 + _dataChunks.Length]; objectsToPin[_dataChunks.Length] = _dataChunks; int chunkHeaderOffset = 0; byte[]? chunkHeaderBuffer = null; if (chunked) { chunkHeaderBuffer = GetChunkHeader(size, out chunkHeaderOffset); _dataChunks[0] = default; _dataChunks[0].DataChunkType = Interop.HttpApi.HTTP_DATA_CHUNK_TYPE.HttpDataChunkFromMemory; _dataChunks[0].BufferLength = (uint)(chunkHeaderBuffer.Length - chunkHeaderOffset); objectsToPin[0] = chunkHeaderBuffer; _dataChunks[1] = default; _dataChunks[1].DataChunkType = Interop.HttpApi.HTTP_DATA_CHUNK_TYPE.HttpDataChunkFromMemory; _dataChunks[1].BufferLength = (uint)size; objectsToPin[1] = buffer; _dataChunks[2] = default; _dataChunks[2].DataChunkType = Interop.HttpApi.HTTP_DATA_CHUNK_TYPE.HttpDataChunkFromMemory; _dataChunks[2].BufferLength = (uint)s_CRLFArray.Length; objectsToPin[2] = s_CRLFArray; } else { _dataChunks[0] = default; _dataChunks[0].DataChunkType = Interop.HttpApi.HTTP_DATA_CHUNK_TYPE.HttpDataChunkFromMemory; _dataChunks[0].BufferLength = (uint)size; objectsToPin[0] = buffer; } // This call will pin needed memory _pOverlapped = boundHandle.AllocateNativeOverlapped(s_IOCallback, state: this, pinData: objectsToPin); if (chunked) { _dataChunks[0].pBuffer = (byte *)(Marshal.UnsafeAddrOfPinnedArrayElement(chunkHeaderBuffer !, chunkHeaderOffset)); _dataChunks[1].pBuffer = (byte *)(Marshal.UnsafeAddrOfPinnedArrayElement(buffer, offset)); _dataChunks[2].pBuffer = (byte *)(Marshal.UnsafeAddrOfPinnedArrayElement(s_CRLFArray, 0)); } else { _dataChunks[0].pBuffer = (byte *)(Marshal.UnsafeAddrOfPinnedArrayElement(buffer, offset)); } } }
protected override bool CheckValid(ResponseDescription response, ref int validThrough, ref int completeLength) { if (NetEventSource.Log.IsEnabled()) { NetEventSource.Info(this, $"CheckValid({response.StatusBuffer})"); } // If the response is less than 4 bytes long, it is too short to tell, so return true, valid so far. if (response.StatusBuffer.Length < 4) { return(true); } string responseString = response.StatusBuffer.ToString(); // Otherwise, if there is no status code for this response yet, get one. if (response.Status == ResponseDescription.NoStatus) { // If the response does not start with three digits, then it is not a valid response from an FTP server. if (!(char.IsDigit(responseString[0]) && char.IsDigit(responseString[1]) && char.IsDigit(responseString[2]) && (responseString[3] == ' ' || responseString[3] == '-'))) { return(false); } else { response.StatusCodeString = responseString.Substring(0, 3); response.Status = Convert.ToInt16(response.StatusCodeString, NumberFormatInfo.InvariantInfo); } // IF a hyphen follows the status code on the first line of the response, then we have a multiline response coming. if (responseString[3] == '-') { response.Multiline = true; } } // If a complete line of response has been received from the server, then see if the // overall response is complete. // If this was not a multiline response, then the response is complete at the end of the line. // If this was a multiline response (indicated by three digits followed by a '-' in the first line), // then we see if the last line received started with the same three digits followed by a space. // If it did, then this is the sign of a complete multiline response. // If the line contained three other digits followed by the response, then this is a violation of the // FTP protocol for multiline responses. // All other cases indicate that the response is not yet complete. int index = 0; while ((index = responseString.IndexOf("\r\n", validThrough, StringComparison.Ordinal)) != -1) // gets the end line. { int lineStart = validThrough; validThrough = index + 2; // validThrough now marks the end of the line being examined. if (!response.Multiline) { completeLength = validThrough; return(true); } if (responseString.Length > lineStart + 4) { // If the first three characters of the response line currently being examined // match the status code, then if they are followed by a space, then we // have reached the end of the reply. if (responseString.Substring(lineStart, 3) == response.StatusCodeString) { if (responseString[lineStart + 3] == ' ') { completeLength = validThrough; return(true); } } } } return(true); }
internal byte[]? GetOutgoingBlob(byte[]?incomingBlob, bool throwOnError, out SecurityStatusPal statusCode) { if (NetEventSource.IsEnabled) { NetEventSource.Enter(this, incomingBlob); } byte[]? result = new byte[_tokenSize]; bool firstTime = _securityContext == null; try { if (!_isServer) { // client session statusCode = NegotiateStreamPal.InitializeSecurityContext( ref _credentialsHandle !, ref _securityContext, _spn, _requestedContextFlags, incomingBlob, _channelBinding, ref result, ref _contextFlags); if (NetEventSource.IsEnabled) { NetEventSource.Info(this, $"SSPIWrapper.InitializeSecurityContext() returns statusCode:0x{((int)statusCode.ErrorCode):x8} ({statusCode})"); } if (statusCode.ErrorCode == SecurityStatusPalErrorCode.CompleteNeeded) { statusCode = NegotiateStreamPal.CompleteAuthToken(ref _securityContext, result); if (NetEventSource.IsEnabled) { NetEventSource.Info(this, $"SSPIWrapper.CompleteAuthToken() returns statusCode:0x{((int)statusCode.ErrorCode):x8} ({statusCode})"); } result = null; } } else { // Server session. statusCode = NegotiateStreamPal.AcceptSecurityContext( _credentialsHandle, ref _securityContext, _requestedContextFlags, incomingBlob, _channelBinding, ref result, ref _contextFlags); if (NetEventSource.IsEnabled) { NetEventSource.Info(this, $"SSPIWrapper.AcceptSecurityContext() returns statusCode:0x{((int)statusCode.ErrorCode):x8} ({statusCode})"); } } } finally { // // Assuming the ISC or ASC has referenced the credential on the first successful call, // we want to decrement the effective ref count by "disposing" it. // The real dispose will happen when the security context is closed. // Note if the first call was not successful the handle is physically destroyed here. // if (firstTime) { _credentialsHandle?.Dispose(); } } if (((int)statusCode.ErrorCode >= (int)SecurityStatusPalErrorCode.OutOfMemory)) { CloseContext(); _isCompleted = true; if (throwOnError) { Exception exception = NegotiateStreamPal.CreateExceptionFromError(statusCode); if (NetEventSource.IsEnabled) { NetEventSource.Exit(this, exception); } throw exception; } if (NetEventSource.IsEnabled) { NetEventSource.Exit(this, $"null statusCode:0x{((int)statusCode.ErrorCode):x8} ({statusCode})"); } return(null); } else if (firstTime && _credentialsHandle != null) { // Cache until it is pushed out by newly incoming handles. SSPIHandleCache.CacheCredential(_credentialsHandle); } // The return value will tell us correctly if the handshake is over or not if (statusCode.ErrorCode == SecurityStatusPalErrorCode.OK || (_isServer && statusCode.ErrorCode == SecurityStatusPalErrorCode.CompleteNeeded)) { // Success. _isCompleted = true; } else if (NetEventSource.IsEnabled) { // We need to continue. if (NetEventSource.IsEnabled) { NetEventSource.Info(this, $"need continue statusCode:0x{((int)statusCode.ErrorCode):x8} ({statusCode}) _securityContext:{_securityContext}"); } } if (NetEventSource.IsEnabled) { if (NetEventSource.IsEnabled) { NetEventSource.Exit(this, $"IsCompleted: {IsCompleted}"); } } return(result); }
// Helpers for async GetHostByName, ResolveToAddresses, and Resolve - they're almost identical // If hostName is an IPString and justReturnParsedIP==true then no reverse lookup will be attempted, but the original address is returned. private static IAsyncResult HostResolutionBeginHelper(string hostName, bool justReturnParsedIp, bool throwOnIIPAny, AsyncCallback requestCallback, object state) { if (hostName == null) { throw new ArgumentNullException(nameof(hostName)); } if (NetEventSource.IsEnabled) { NetEventSource.Info(null, hostName); } // See if it's an IP Address. IPAddress ipAddress; DnsResolveAsyncResult asyncResult; if (IPAddress.TryParse(hostName, out ipAddress)) { if (throwOnIIPAny && (ipAddress.Equals(IPAddress.Any) || ipAddress.Equals(IPAddress.IPv6Any))) { throw new ArgumentException(SR.net_invalid_ip_addr, nameof(hostName)); } asyncResult = new DnsResolveAsyncResult(ipAddress, null, state, requestCallback); if (justReturnParsedIp) { IPHostEntry hostEntry = NameResolutionUtilities.GetUnresolvedAnswer(ipAddress); asyncResult.StartPostingAsyncOp(false); asyncResult.InvokeCallback(hostEntry); asyncResult.FinishPostingAsyncOp(); return(asyncResult); } } else { asyncResult = new DnsResolveAsyncResult(hostName, null, state, requestCallback); } // Set up the context, possibly flow. asyncResult.StartPostingAsyncOp(false); // If the OS supports it and 'hostName' is not an IP Address, resolve the name asynchronously // instead of calling the synchronous version in the ThreadPool. if (NameResolutionPal.SupportsGetAddrInfoAsync && ipAddress == null) { ValidateHostName(hostName); NameResolutionPal.GetAddrInfoAsync(asyncResult); } else { // Start the resolve. Task.Factory.StartNew( s => ResolveCallback(s), asyncResult, CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default); } // Finish the flowing, maybe it completed? This does nothing if we didn't initiate the flowing above. asyncResult.FinishPostingAsyncOp(); return(asyncResult); }
private bool CaptureOrComplete(ref ExecutionContext?cachedContext, bool returnContext) { if ((_flags & StateFlags.PostBlockStarted) == 0) { NetEventSource.Fail(this, "Called without calling StartPostingAsyncOp."); } // See if we're going to need to capture the context. bool capturingContext = AsyncCallback != null || (_flags & StateFlags.CaptureContext) != 0; // Peek if we've already completed, but don't fix CompletedSynchronously yet // Capture the identity if requested, unless we're going to capture the context anyway, unless // capturing the context won't be sufficient. if ((_flags & StateFlags.CaptureIdentity) != 0 && !InternalPeekCompleted && (!capturingContext)) { if (NetEventSource.IsEnabled) { NetEventSource.Info(this, "starting identity capture"); } SafeCaptureIdentity(); } // No need to flow if there's no callback, unless it's been specifically requested. // Note that Capture() can return null, for example if SuppressFlow() is in effect. if (capturingContext && !InternalPeekCompleted) { if (NetEventSource.IsEnabled) { NetEventSource.Info(this, "starting capture"); } if (cachedContext == null) { cachedContext = ExecutionContext.Capture(); } if (cachedContext != null) { if (!returnContext) { _context = cachedContext; cachedContext = null; } else { _context = cachedContext; } } if (NetEventSource.IsEnabled) { NetEventSource.Info(this, $"_context:{_context}"); } } else { // Otherwise we have to have completed synchronously, or not needed the context. if (NetEventSource.IsEnabled) { NetEventSource.Info(this, "Skipping capture"); } cachedContext = null; if (AsyncCallback != null && !CompletedSynchronously) { NetEventSource.Fail(this, "Didn't capture context, but didn't complete synchronously!"); } } // Now we want to see for sure what to do. We might have just captured the context for no reason. // This has to be the first time the state has been queried "for real" (apart from InvokeCallback) // to guarantee synchronization with Complete() (otherwise, Complete() could try to call the // callback without the context having been gotten). DebugProtectState(false); if (CompletedSynchronously) { if (NetEventSource.IsEnabled) { NetEventSource.Info(this, "Completing synchronously"); } base.Complete(IntPtr.Zero); return(true); } return(false); }
/// <summary> /// ReceiveCommandResponseCallback is the main "while loop" of the ReceiveCommandResponse function family. /// In general, what is does is perform an EndReceive() to complete the previous retrieval of bytes from the /// server (unless it is using a buffered response) It then processes what is received by using the /// implementing class's CheckValid() function, as described above. If the response is complete, it returns the single complete /// response in the GeneralResponseDescription created in BeginReceiveComamndResponse, and buffers the rest as described above. /// /// If the response is not complete, it issues another Connection.BeginReceive, with callback ReceiveCommandResponse2, /// so the action will continue at the next invocation of ReceiveCommandResponse2. /// </summary> private void ReceiveCommandResponseCallback(ReceiveState state, int bytesRead) { // completeLength will be set to a nonnegative number by CheckValid if the response is complete: // it will set completeLength to the length of a complete response. int completeLength = -1; while (true) { int validThrough = state.ValidThrough; // passed to checkvalid // If we have a Buffered response (ie data was received with the last response that was past the end of that response) // deal with it as if we had just received it now instead of actually doing another receive if (_buffer.Length > 0) { // Append the string we got from the buffer, and flush it out. state.Resp.StatusBuffer.Append(_buffer); _buffer = string.Empty; // invoke checkvalid. if (!CheckValid(state.Resp, ref validThrough, ref completeLength)) { throw GenerateException(SR.net_ftp_protocolerror, WebExceptionStatus.ServerProtocolViolation, null); } } else // we did a Connection.BeginReceive. Note that in this case, all bytes received are in the receive buffer (because bytes from // the buffer were transferred there if necessary { // this indicates the connection was closed. if (bytesRead <= 0) { throw GenerateException(SR.net_ftp_protocolerror, WebExceptionStatus.ServerProtocolViolation, null); } // decode the bytes in the receive buffer into a string, append it to the statusbuffer, and invoke checkvalid. // Decoder automatically takes care of caching partial codepoints at the end of a buffer. char[] chars = new char[_decoder.GetCharCount(state.Buffer, 0, bytesRead)]; int numChars = _decoder.GetChars(state.Buffer, 0, bytesRead, chars, 0, false); string szResponse = new string(chars, 0, numChars); state.Resp.StatusBuffer.Append(szResponse); if (!CheckValid(state.Resp, ref validThrough, ref completeLength)) { throw GenerateException(SR.net_ftp_protocolerror, WebExceptionStatus.ServerProtocolViolation, null); } // If the response is complete, then determine how many characters are left over...these bytes need to be set into Buffer. if (completeLength >= 0) { int unusedChars = state.Resp.StatusBuffer.Length - completeLength; if (unusedChars > 0) { _buffer = szResponse.Substring(szResponse.Length - unusedChars, unusedChars); } } } // Now, in general, if the response is not complete, update the "valid through" length for the efficiency of checkValid, // and perform the next receive. // Note that there may NOT be bytes in the beginning of the receive buffer (even if there were partial characters left over after the // last encoding), because they get tracked in the Decoder. if (completeLength < 0) { state.ValidThrough = validThrough; try { if (_isAsync) { BeginRead(state.Buffer, 0, state.Buffer.Length, s_readCallbackDelegate, state); return; } else { bytesRead = Read(state.Buffer, 0, state.Buffer.Length); if (bytesRead == 0) { CloseSocket(); } continue; } } catch (IOException) { MarkAsRecoverableFailure(); throw; } catch { throw; } } // The response is completed break; } // Otherwise, we have a complete response. string responseString = state.Resp.StatusBuffer.ToString(); state.Resp.StatusDescription = responseString.Substring(0, completeLength); // Set the StatusDescription to the complete part of the response. Note that the Buffer has already been taken care of above. if (NetEventSource.IsEnabled) { NetEventSource.Info(this, $"Received response: {responseString.Substring(0, completeLength - 2)}"); } if (_isAsync) { // Tell who is listening what was received. if (state.Resp != null) { _currentResponseDescription = state.Resp; } Stream stream = null; if (PostReadCommandProcessing(ref stream)) { return; } ContinueCommandPipeline(); } }
// // Security: We temporarily reset thread token to open the cert store under process account. // internal static X509Store EnsureStoreOpened(bool isMachineStore) { X509Store store = isMachineStore ? s_myMachineCertStoreEx : s_myCertStoreEx; // TODO #3862 Investigate if this can be switched to either the static or Lazy<T> patterns. if (store == null) { lock (s_syncObject) { store = isMachineStore ? s_myMachineCertStoreEx : s_myCertStoreEx; if (store == null) { // NOTE: that if this call fails we won't keep track and the next time we enter we will try to open the store again. StoreLocation storeLocation = isMachineStore ? StoreLocation.LocalMachine : StoreLocation.CurrentUser; store = new X509Store(StoreName.My, storeLocation); try { // For app-compat We want to ensure the store is opened under the **process** account. try { WindowsIdentity.RunImpersonated(SafeAccessTokenHandle.InvalidHandle, () => { store.Open(OpenFlags.ReadOnly | OpenFlags.OpenExistingOnly); if (NetEventSource.IsEnabled) { NetEventSource.Info(null, $"storeLocation {storeLocation} returned store: {store}"); } }); } catch { throw; } if (isMachineStore) { s_myMachineCertStoreEx = store; } else { s_myCertStoreEx = store; } return(store); } catch (Exception exception) { if (exception is CryptographicException || exception is SecurityException) { NetEventSource.Fail(null, $"Failed to open cert store, location: {storeLocation} exception: {exception}"); return(null); } if (NetEventSource.IsEnabled) { NetEventSource.Error(null, SR.Format(SR.net_log_open_store_failed, storeLocation, exception)); } throw; } } } } return(store); }
void ICloseEx.CloseEx(CloseExState closeState) { if (NetEventSource.IsEnabled) { NetEventSource.Info(this, $"state = {closeState}"); } lock (this) { if (_closing == true) { return; } _closing = true; _writeable = false; _readable = false; } try { try { if ((closeState & CloseExState.Abort) == 0) { _networkStream.Close(DefaultCloseTimeout); } else { _networkStream.Close(0); } } finally { _request.DataStreamClosed(closeState); } } catch (Exception exception) { bool doThrow = true; WebException webException = exception as WebException; if (webException != null) { FtpWebResponse response = webException.Response as FtpWebResponse; if (response != null) { if (!_isFullyRead && response.StatusCode == FtpStatusCode.ConnectionClosed) { doThrow = false; } } } if (doThrow) { if ((closeState & CloseExState.Silent) == 0) { throw; } } } }
// This is called by underlying base class code, each time a new response is received from the wire or a protocol stage is resumed. // This function controls the setting up of a data socket/connection, and of saving off the server responses. protected override PipelineInstruction PipelineCallback(PipelineEntry?entry, ResponseDescription?response, bool timeout, ref Stream?stream) { if (NetEventSource.Log.IsEnabled()) { NetEventSource.Info(this, $"Command:{entry?.Command} Description:{response?.StatusDescription}"); } // null response is not expected if (response == null) { return(PipelineInstruction.Abort); } FtpStatusCode status = (FtpStatusCode)response.Status; // // Update global "current status" for FtpWebRequest // if (status != FtpStatusCode.ClosingControl) { // A 221 status won't be reflected on the user FTP response // Anything else will (by design?) StatusCode = status; StatusLine = response.StatusDescription; } // If the status code is outside the range defined in RFC (1xx to 5xx) throw if (response.InvalidStatusCode) { throw new WebException(SR.net_InvalidStatusCode, WebExceptionStatus.ProtocolError); } // Update the banner message if any, this is a little hack because the "entry" param is null if (_index == -1) { if (status == FtpStatusCode.SendUserCommand) { _bannerMessage = new StringBuilder(); _bannerMessage.Append(StatusLine); return(PipelineInstruction.Advance); } else if (status == FtpStatusCode.ServiceTemporarilyNotAvailable) { return(PipelineInstruction.Reread); } else { throw GenerateException(status, response.StatusDescription, null); } } // // Check for the result of our attempt to use UTF8 // if (entry !.Command == "OPTS utf8 on\r\n") { if (response.PositiveCompletion) { Encoding = Encoding.UTF8; } else { Encoding = Encoding.Default; } return(PipelineInstruction.Advance); } // If we are already logged in and the server returns 530 then // the server does not support re-issuing a USER command, // tear down the connection and start all over again if (entry.Command.IndexOf("USER", StringComparison.Ordinal) != -1) { // The server may not require a password for this user, so bypass the password command if (status == FtpStatusCode.LoggedInProceed) { _loginState = FtpLoginState.LoggedIn; _index++; } } // // Throw on an error with possible recovery option // if (response.TransientFailure || response.PermanentFailure) { if (status == FtpStatusCode.ServiceNotAvailable) { MarkAsRecoverableFailure(); } throw GenerateException(status, response.StatusDescription, null); } if (_loginState != FtpLoginState.LoggedIn && entry.Command.IndexOf("PASS", StringComparison.Ordinal) != -1) { // Note the fact that we logged in if (status == FtpStatusCode.NeedLoginAccount || status == FtpStatusCode.LoggedInProceed) { _loginState = FtpLoginState.LoggedIn; } else { throw GenerateException(status, response.StatusDescription, null); } } // // Parse special cases // if (entry.HasFlag(PipelineEntryFlags.CreateDataConnection) && (response.PositiveCompletion || response.PositiveIntermediate)) { bool isSocketReady; PipelineInstruction result = QueueOrCreateDataConection(entry, response, timeout, ref stream, out isSocketReady); if (!isSocketReady) { return(result); } // otherwise we have a stream to create } // // This is part of the above case and it's all about giving data stream back // if (status == FtpStatusCode.OpeningData || status == FtpStatusCode.DataAlreadyOpen) { if (_dataSocket == null) { return(PipelineInstruction.Abort); } if (!entry.HasFlag(PipelineEntryFlags.GiveDataStream)) { _abortReason = SR.Format(SR.net_ftp_invalid_status_response, status, entry.Command); return(PipelineInstruction.Abort); } // Parse out the Content length, if we can TryUpdateContentLength(response.StatusDescription !); // Parse out the file name, when it is returned and use it for our ResponseUri FtpWebRequest request = (FtpWebRequest)_request !; if (request.MethodInfo.ShouldParseForResponseUri) { TryUpdateResponseUri(response.StatusDescription !, request); } return(QueueOrCreateFtpDataStream(ref stream)); } // // Parse responses by status code exclusivelly // // Update welcome message if (status == FtpStatusCode.LoggedInProceed) { _welcomeMessage !.Append(StatusLine); } // OR set the user response ExitMessage else if (status == FtpStatusCode.ClosingControl) { _exitMessage !.Append(response.StatusDescription); // And close the control stream socket on "QUIT" CloseSocket(); } // OR set us up for SSL/TLS, after this we'll be writing securely else if (status == FtpStatusCode.ServerWantsSecureSession) { // If NetworkStream is a TlsStream, then this must be in the async callback // from completing the SSL handshake. // So just let the pipeline continue. if (!(NetworkStream is TlsStream)) { FtpWebRequest request = (FtpWebRequest)_request !; TlsStream tlsStream = new TlsStream(NetworkStream, Socket, request.RequestUri.Host, request.ClientCertificates); if (_isAsync) { tlsStream.BeginAuthenticateAsClient(ar => { try { tlsStream.EndAuthenticateAsClient(ar); NetworkStream = tlsStream; this.ContinueCommandPipeline(); } catch (Exception e) { this.CloseSocket(); this.InvokeRequestCallback(e); } }, null); return(PipelineInstruction.Pause); } else { tlsStream.AuthenticateAsClient(); NetworkStream = tlsStream; } } } // OR parse out the file size or file time, usually a result of sending SIZE/MDTM commands else if (status == FtpStatusCode.FileStatus) { if (entry.Command.StartsWith("SIZE ", StringComparison.Ordinal)) { _contentLength = GetContentLengthFrom213Response(response.StatusDescription !); } else if (entry.Command.StartsWith("MDTM ", StringComparison.Ordinal)) { _lastModified = GetLastModifiedFrom213Response(response.StatusDescription !); } } // OR parse out our login directory else if (status == FtpStatusCode.PathnameCreated) { if (entry.Command == "PWD\r\n" && !entry.HasFlag(PipelineEntryFlags.UserCommand)) { _loginDirectory = GetLoginDirectory(response.StatusDescription !); } } // Asserting we have some positive response else { // We only use CWD to reset ourselves back to the login directory. if (entry.Command.IndexOf("CWD", StringComparison.Ordinal) != -1) { _establishedServerDirectory = _requestedServerDirectory; } } // Intermediate responses require rereading if (response.PositiveIntermediate || (!UsingSecureStream && entry.Command == "AUTH TLS\r\n")) { return(PipelineInstruction.Reread); } return(PipelineInstruction.Advance); }
private IAsyncResult BeginWriteCore(byte[] buffer, int offset, int size, AsyncCallback callback, object state) { Interop.HttpApi.HTTP_FLAGS flags = ComputeLeftToWrite(); if (_closed || (size == 0 && _leftToWrite != 0)) { if (NetEventSource.IsEnabled) { NetEventSource.Exit(this); } HttpResponseStreamAsyncResult result = new HttpResponseStreamAsyncResult(this, state, callback); result.InvokeCallback((uint)0); return(result); } if (_leftToWrite >= 0 && size > _leftToWrite) { throw new ProtocolViolationException(SR.net_entitytoobig); } uint statusCode; uint bytesSent = 0; flags |= _leftToWrite == size ? Interop.HttpApi.HTTP_FLAGS.NONE : Interop.HttpApi.HTTP_FLAGS.HTTP_SEND_RESPONSE_FLAG_MORE_DATA; bool sentHeaders = _httpContext.Response.SentHeaders; HttpResponseStreamAsyncResult asyncResult = new HttpResponseStreamAsyncResult(this, state, callback, buffer, offset, size, _httpContext.Response.BoundaryType == BoundaryType.Chunked, sentHeaders, _httpContext.RequestQueueBoundHandle); // Update m_LeftToWrite now so we can queue up additional BeginWrite's without waiting for EndWrite. UpdateAfterWrite((uint)((_httpContext.Response.BoundaryType == BoundaryType.Chunked) ? 0 : size)); try { if (!sentHeaders) { statusCode = _httpContext.Response.SendHeaders(null, asyncResult, flags, false); } else { if (NetEventSource.IsEnabled) { NetEventSource.Info(this, "Calling Interop.HttpApi.HttpSendResponseEntityBody"); } statusCode = Interop.HttpApi.HttpSendResponseEntityBody( _httpContext.RequestQueueHandle, _httpContext.RequestId, (uint)flags, asyncResult.dataChunkCount, asyncResult.pDataChunks, &bytesSent, SafeLocalAllocHandle.Zero, 0, asyncResult._pOverlapped, null); if (NetEventSource.IsEnabled) { NetEventSource.Info(this, "Call to Interop.HttpApi.HttpSendResponseEntityBody returned:" + statusCode); } } } catch (Exception e) { if (NetEventSource.IsEnabled) { NetEventSource.Error(this, e.ToString()); } asyncResult.InternalCleanup(); _closed = true; _httpContext.Abort(); throw; } if (statusCode != Interop.HttpApi.ERROR_SUCCESS && statusCode != Interop.HttpApi.ERROR_IO_PENDING) { asyncResult.InternalCleanup(); if (_httpContext.Listener.IgnoreWriteExceptions && sentHeaders) { if (NetEventSource.IsEnabled) { NetEventSource.Info(this, "BeginWrite() Suppressing error"); } } else { Exception exception = new HttpListenerException((int)statusCode); if (NetEventSource.IsEnabled) { NetEventSource.Error(this, exception.ToString()); } _closed = true; _httpContext.Abort(); throw exception; } } if (statusCode == Interop.HttpApi.ERROR_SUCCESS && HttpListener.SkipIOCPCallbackOnSuccess) { // IO operation completed synchronously - callback won't be called to signal completion. asyncResult.IOCompleted(statusCode, bytesSent); } // Last write, cache it for special cancelation handling. if ((flags & Interop.HttpApi.HTTP_FLAGS.HTTP_SEND_RESPONSE_FLAG_MORE_DATA) == 0) { _lastWrite = asyncResult; } if (NetEventSource.IsEnabled) { NetEventSource.Exit(this); } return(asyncResult); }
/// <summary> /// <para>Creates an array of commands, that will be sent to the server</para> /// </summary> protected override PipelineEntry[] BuildCommandsList(WebRequest req) { bool resetLoggedInState = false; FtpWebRequest request = (FtpWebRequest)req; if (NetEventSource.Log.IsEnabled()) { NetEventSource.Info(this); } _responseUri = request.RequestUri; ArrayList commandList = new ArrayList(); if (request.EnableSsl && !UsingSecureStream) { commandList.Add(new PipelineEntry(FormatFtpCommand("AUTH", "TLS"))); // According to RFC we need to re-authorize with USER/PASS after we re-authenticate. resetLoggedInState = true; } if (resetLoggedInState) { _loginDirectory = null; _establishedServerDirectory = null; _requestedServerDirectory = null; _currentTypeSetting = string.Empty; if (_loginState == FtpLoginState.LoggedIn) { _loginState = FtpLoginState.LoggedInButNeedsRelogin; } } if (_loginState != FtpLoginState.LoggedIn) { Credentials = request.Credentials !.GetCredential(request.RequestUri, "basic"); _welcomeMessage = new StringBuilder(); _exitMessage = new StringBuilder(); string domainUserName = string.Empty; string password = string.Empty; if (Credentials != null) { domainUserName = Credentials.UserName; string domain = Credentials.Domain; if (!string.IsNullOrEmpty(domain)) { domainUserName = domain + "\\" + domainUserName; } password = Credentials.Password; } if (domainUserName.Length == 0 && password.Length == 0) { domainUserName = "******"; password = "******"; } commandList.Add(new PipelineEntry(FormatFtpCommand("USER", domainUserName))); commandList.Add(new PipelineEntry(FormatFtpCommand("PASS", password), PipelineEntryFlags.DontLogParameter)); // If SSL, always configure data channel encryption after authentication to maximum RFC compatibility. The RFC allows for // PBSZ/PROT commands to come either before or after the USER/PASS, but some servers require USER/PASS immediately after // the AUTH TLS command. if (request.EnableSsl && !UsingSecureStream) { commandList.Add(new PipelineEntry(FormatFtpCommand("PBSZ", "0"))); commandList.Add(new PipelineEntry(FormatFtpCommand("PROT", "P"))); } commandList.Add(new PipelineEntry(FormatFtpCommand("OPTS", "utf8 on"))); commandList.Add(new PipelineEntry(FormatFtpCommand("PWD", null))); } GetPathOption getPathOption = GetPathOption.Normal; if (request.MethodInfo.HasFlag(FtpMethodFlags.DoesNotTakeParameter)) { getPathOption = GetPathOption.AssumeNoFilename; } else if (request.MethodInfo.HasFlag(FtpMethodFlags.ParameterIsDirectory)) { getPathOption = GetPathOption.AssumeFilename; } string requestPath; string requestDirectory; string requestFilename; GetPathInfo(getPathOption, request.RequestUri, out requestPath, out requestDirectory, out requestFilename); if (requestFilename.Length == 0 && request.MethodInfo.HasFlag(FtpMethodFlags.TakesParameter)) { throw new WebException(SR.net_ftp_invalid_uri); } // We optimize for having the current working directory staying at the login directory. This ensure that // our relative paths work right and reduces unnecessary CWD commands. // Usually, we don't change the working directory except for some FTP commands. If necessary, // we need to reset our working directory back to the login directory. if (_establishedServerDirectory != null && _loginDirectory != null && _establishedServerDirectory != _loginDirectory) { commandList.Add(new PipelineEntry(FormatFtpCommand("CWD", _loginDirectory), PipelineEntryFlags.UserCommand)); _requestedServerDirectory = _loginDirectory; } // For most commands, we don't need to navigate to the directory since we pass in the full // path as part of the FTP protocol command. However, some commands require it. if (request.MethodInfo.HasFlag(FtpMethodFlags.MustChangeWorkingDirectoryToPath) && requestDirectory.Length > 0) { commandList.Add(new PipelineEntry(FormatFtpCommand("CWD", requestDirectory), PipelineEntryFlags.UserCommand)); _requestedServerDirectory = requestDirectory; } if (!request.MethodInfo.IsCommandOnly) { string requestedTypeSetting = request.UseBinary ? "I" : "A"; if (_currentTypeSetting != requestedTypeSetting) { commandList.Add(new PipelineEntry(FormatFtpCommand("TYPE", requestedTypeSetting))); _currentTypeSetting = requestedTypeSetting; } if (request.UsePassive) { string passiveCommand = (ServerAddress.AddressFamily == AddressFamily.InterNetwork || ServerAddress.IsIPv4MappedToIPv6) ? "PASV" : "EPSV"; commandList.Add(new PipelineEntry(FormatFtpCommand(passiveCommand, null), PipelineEntryFlags.CreateDataConnection)); } else { string portCommand = (ServerAddress.AddressFamily == AddressFamily.InterNetwork || ServerAddress.IsIPv4MappedToIPv6) ? "PORT" : "EPRT"; CreateFtpListenerSocket(request); commandList.Add(new PipelineEntry(FormatFtpCommand(portCommand, GetPortCommandLine(request)))); } if (request.ContentOffset > 0) { // REST command must always be the last sent before the main file command is sent. commandList.Add(new PipelineEntry(FormatFtpCommand("REST", request.ContentOffset.ToString(CultureInfo.InvariantCulture)))); } } PipelineEntryFlags flags = PipelineEntryFlags.UserCommand; if (!request.MethodInfo.IsCommandOnly) { flags |= PipelineEntryFlags.GiveDataStream; if (!request.UsePassive) { flags |= PipelineEntryFlags.CreateDataConnection; } } if (request.MethodInfo.Operation == FtpOperation.Rename) { string baseDir = (requestDirectory.Length == 0) ? string.Empty : requestDirectory + "/"; commandList.Add(new PipelineEntry(FormatFtpCommand("RNFR", baseDir + requestFilename), flags)); string renameTo; if (!string.IsNullOrEmpty(request.RenameTo) && request.RenameTo.StartsWith("/", StringComparison.OrdinalIgnoreCase)) { renameTo = request.RenameTo; // Absolute path } else { renameTo = baseDir + request.RenameTo; // Relative path } commandList.Add(new PipelineEntry(FormatFtpCommand("RNTO", renameTo), flags)); } else if (request.MethodInfo.HasFlag(FtpMethodFlags.DoesNotTakeParameter)) { commandList.Add(new PipelineEntry(FormatFtpCommand(request.Method, string.Empty), flags)); } else if (request.MethodInfo.HasFlag(FtpMethodFlags.MustChangeWorkingDirectoryToPath)) { commandList.Add(new PipelineEntry(FormatFtpCommand(request.Method, requestFilename), flags)); } else { commandList.Add(new PipelineEntry(FormatFtpCommand(request.Method, requestPath), flags)); } commandList.Add(new PipelineEntry(FormatFtpCommand("QUIT", null))); return((PipelineEntry[])commandList.ToArray(typeof(PipelineEntry))); }
private void WriteCore(byte[] buffer, int offset, int size) { Interop.HttpApi.HTTP_FLAGS flags = ComputeLeftToWrite(); if (size == 0 && _leftToWrite != 0) { if (NetEventSource.IsEnabled) { NetEventSource.Exit(this); } return; } if (_leftToWrite >= 0 && size > _leftToWrite) { throw new ProtocolViolationException(SR.net_entitytoobig); } uint statusCode; uint dataToWrite = (uint)size; SafeLocalAllocHandle bufferAsIntPtr = null; IntPtr pBufferAsIntPtr = IntPtr.Zero; bool sentHeaders = _httpContext.Response.SentHeaders; try { if (size == 0) { statusCode = _httpContext.Response.SendHeaders(null, null, flags, false); } else { fixed(byte *pDataBuffer = buffer) { byte *pBuffer = pDataBuffer; if (_httpContext.Response.BoundaryType == BoundaryType.Chunked) { string chunkHeader = size.ToString("x", CultureInfo.InvariantCulture); dataToWrite = dataToWrite + (uint)(chunkHeader.Length + 4); bufferAsIntPtr = SafeLocalAllocHandle.LocalAlloc((int)dataToWrite); pBufferAsIntPtr = bufferAsIntPtr.DangerousGetHandle(); for (int i = 0; i < chunkHeader.Length; i++) { Marshal.WriteByte(pBufferAsIntPtr, i, (byte)chunkHeader[i]); } Marshal.WriteInt16(pBufferAsIntPtr, chunkHeader.Length, 0x0A0D); Marshal.Copy(buffer, offset, pBufferAsIntPtr + chunkHeader.Length + 2, size); Marshal.WriteInt16(pBufferAsIntPtr, (int)(dataToWrite - 2), 0x0A0D); pBuffer = (byte *)pBufferAsIntPtr; offset = 0; } Interop.HttpApi.HTTP_DATA_CHUNK dataChunk = default; dataChunk.DataChunkType = Interop.HttpApi.HTTP_DATA_CHUNK_TYPE.HttpDataChunkFromMemory; dataChunk.pBuffer = (byte *)(pBuffer + offset); dataChunk.BufferLength = dataToWrite; flags |= _leftToWrite == size ? Interop.HttpApi.HTTP_FLAGS.NONE : Interop.HttpApi.HTTP_FLAGS.HTTP_SEND_RESPONSE_FLAG_MORE_DATA; if (!sentHeaders) { statusCode = _httpContext.Response.SendHeaders(&dataChunk, null, flags, false); } else { if (NetEventSource.IsEnabled) { NetEventSource.Info(this, "Calling Interop.HttpApi.HttpSendResponseEntityBody"); } statusCode = Interop.HttpApi.HttpSendResponseEntityBody( _httpContext.RequestQueueHandle, _httpContext.RequestId, (uint)flags, 1, &dataChunk, null, SafeLocalAllocHandle.Zero, 0, null, null); if (NetEventSource.IsEnabled) { NetEventSource.Info(this, "Call to Interop.HttpApi.HttpSendResponseEntityBody returned:" + statusCode); } if (_httpContext.Listener.IgnoreWriteExceptions) { if (NetEventSource.IsEnabled) { NetEventSource.Info(this, "Write() suppressing error"); } statusCode = Interop.HttpApi.ERROR_SUCCESS; } } } } } finally { // free unmanaged buffer bufferAsIntPtr?.Close(); } if (statusCode != Interop.HttpApi.ERROR_SUCCESS && statusCode != Interop.HttpApi.ERROR_HANDLE_EOF) { Exception exception = new HttpListenerException((int)statusCode); if (NetEventSource.IsEnabled) { NetEventSource.Error(this, exception.ToString()); } _closed = true; _httpContext.Abort(); throw exception; } UpdateAfterWrite(dataToWrite); if (NetEventSource.IsEnabled) { NetEventSource.DumpBuffer(this, buffer, offset, (int)dataToWrite); } if (NetEventSource.IsEnabled) { NetEventSource.Exit(this); } }
private PipelineInstruction QueueOrCreateDataConection(PipelineEntry entry, ResponseDescription response, bool timeout, ref Stream?stream, out bool isSocketReady) { isSocketReady = false; if (_dataHandshakeStarted) { isSocketReady = true; return(PipelineInstruction.Pause); //if we already started then this is re-entering into the callback where we proceed with the stream } _dataHandshakeStarted = true; // Handle passive responses by parsing the port and later doing a Connect(...) bool isPassive = false; int port = -1; if (entry.Command == "PASV\r\n" || entry.Command == "EPSV\r\n") { if (!response.PositiveCompletion) { _abortReason = SR.Format(SR.net_ftp_server_failed_passive, response.Status); return(PipelineInstruction.Abort); } if (entry.Command == "PASV\r\n") { port = GetPortV4(response.StatusDescription !); } else { port = GetPortV6(response.StatusDescription !); } isPassive = true; } if (isPassive) { if (port == -1) { NetEventSource.Fail(this, "'port' not set."); } try { _dataSocket = CreateFtpDataSocket((FtpWebRequest)_request !, Socket); } catch (ObjectDisposedException) { throw ExceptionHelper.RequestAbortedException; } IPEndPoint localEndPoint = new IPEndPoint(((IPEndPoint)Socket.LocalEndPoint !).Address, 0); _dataSocket.Bind(localEndPoint); _passiveEndPoint = new IPEndPoint(ServerAddress, port); } PipelineInstruction result; if (_passiveEndPoint != null) { IPEndPoint passiveEndPoint = _passiveEndPoint; _passiveEndPoint = null; if (NetEventSource.Log.IsEnabled()) { NetEventSource.Info(this, "starting Connect()"); } if (_isAsync) { _dataSocket !.BeginConnect(passiveEndPoint, s_connectCallbackDelegate, this); result = PipelineInstruction.Pause; } else { _dataSocket !.Connect(passiveEndPoint); result = PipelineInstruction.Advance; // for passive mode we end up going to the next command } } else { if (NetEventSource.Log.IsEnabled()) { NetEventSource.Info(this, "starting Accept()"); } if (_isAsync) { _dataSocket !.BeginAccept(s_acceptCallbackDelegate, this); result = PipelineInstruction.Pause; } else { Socket listenSocket = _dataSocket !; try { _dataSocket = _dataSocket !.Accept(); if (!ServerAddress.Equals(((IPEndPoint)_dataSocket.RemoteEndPoint !).Address)) { _dataSocket.Close(); throw new WebException(SR.net_ftp_active_address_different, WebExceptionStatus.ProtocolError); } isSocketReady = true; // for active mode we end up creating a stream before advancing the pipeline result = PipelineInstruction.Pause; } finally { listenSocket.Close(); } } } return(result); }
/// <summary> /// <para>Fires the timer if it is still active and has expired. Returns /// true if it can be deleted, or false if it is still timing.</para> /// </summary> internal bool Fire() { if (_timerState == TimerState.Sentinel) { if (NetEventSource.IsEnabled) { NetEventSource.Info(this, "TimerQueue tried to Fire a Sentinel."); } } if (_timerState != TimerState.Ready) { return(true); } // Must get the current tick count within this method so it is guaranteed not to be before // StartTime, which is set in the constructor. int nowMilliseconds = Environment.TickCount; if (IsTickBetween(StartTime, Expiration, nowMilliseconds)) { if (NetEventSource.IsEnabled) { NetEventSource.Info(this, $"TimerThreadTimer#{StartTime}::Fire() Not firing ({StartTime} <= {nowMilliseconds} < {Expiration})"); } return(false); } bool needCallback = false; lock (_queueLock) { if (_timerState == TimerState.Ready) { if (NetEventSource.IsEnabled) { NetEventSource.Info(this, $"TimerThreadTimer#{StartTime}::Fire() Firing ({StartTime} <= {nowMilliseconds} >= " + Expiration + ")"); } _timerState = TimerState.Fired; // Remove it from the list. Next.Prev = Prev; Prev.Next = Next; Next = null; Prev = null; needCallback = _callback != null; } } if (needCallback) { try { Callback callback = _callback; object context = _context; _callback = null; _context = null; callback(this, nowMilliseconds, context); } catch (Exception exception) { if (ExceptionCheck.IsFatal(exception)) { throw; } if (NetEventSource.IsEnabled) { NetEventSource.Error(this, $"exception in callback: {exception}"); } // This thread is not allowed to go into user code, so we should never get an exception here. // So, in debug, throw it up, killing the AppDomain. In release, we'll just ignore it. #if DEBUG throw; #endif } } return(true); }
internal static X509Store EnsureStoreOpened(bool isMachineStore) { X509Store store = isMachineStore ? s_myMachineCertStoreEx : s_myCertStoreEx; if (store == null) { StoreLocation storeLocation = isMachineStore ? StoreLocation.LocalMachine : StoreLocation.CurrentUser; // On Windows and OSX CheckSupportsStore is not defined, so the call is eliminated and the // if should be folded out. // // On Unix it will prevent the lock from being held and released over and over for the LocalMachine store. bool supportsStore = true; CheckSupportsStore(storeLocation, ref supportsStore); if (!supportsStore) { return(null); } lock (s_syncObject) { store = isMachineStore ? s_myMachineCertStoreEx : s_myCertStoreEx; if (store == null) { try { // NOTE: that if this call fails we won't keep track and the next time we enter we will try to open the store again. store = OpenStore(storeLocation); if (NetEventSource.IsEnabled) { NetEventSource.Info(null, $"storeLocation: {storeLocation} returned store {store}"); } if (isMachineStore) { s_myMachineCertStoreEx = store; } else { s_myCertStoreEx = store; } } catch (Exception exception) { if (exception is CryptographicException || exception is SecurityException) { NetEventSource.Fail(null, $"Failed to open cert store, location: {storeLocation} exception: {exception}"); return(null); } if (NetEventSource.IsEnabled) { NetEventSource.Error(null, SR.Format(SR.net_log_open_store_failed, storeLocation, exception)); } throw; } } } } return(store); }
/* * 12.3 * HttpSendHttpResponse() and HttpSendResponseEntityBody() Flag Values. * The following flags can be used on calls to HttpSendHttpResponse() and HttpSendResponseEntityBody() API calls: * #define HTTP_SEND_RESPONSE_FLAG_DISCONNECT 0x00000001 #define HTTP_SEND_RESPONSE_FLAG_MORE_DATA 0x00000002 #define HTTP_SEND_RESPONSE_FLAG_RAW_HEADER 0x00000004 #define HTTP_SEND_RESPONSE_FLAG_VALID 0x00000007 * * HTTP_SEND_RESPONSE_FLAG_DISCONNECT: * specifies that the network connection should be disconnected immediately after * sending the response, overriding the HTTP protocol's persistent connection features. * HTTP_SEND_RESPONSE_FLAG_MORE_DATA: * specifies that additional entity body data will be sent by the caller. Thus, * the last call HttpSendResponseEntityBody for a RequestId, will have this flag reset. * HTTP_SEND_RESPONSE_RAW_HEADER: * specifies that a caller of HttpSendResponseEntityBody() is intentionally omitting * a call to HttpSendHttpResponse() in order to bypass normal header processing. The * actual HTTP header will be generated by the application and sent as entity body. * This flag should be passed on the first call to HttpSendResponseEntityBody, and * not after. Thus, flag is not applicable to HttpSendHttpResponse. */ internal unsafe uint SendHeaders(Interop.HttpApi.HTTP_DATA_CHUNK *pDataChunk, HttpResponseStreamAsyncResult asyncResult, Interop.HttpApi.HTTP_FLAGS flags, bool isWebSocketHandshake) { if (NetEventSource.IsEnabled) { NetEventSource.Info(this, $"pDataChunk: { ((IntPtr)pDataChunk)}, asyncResult: {asyncResult}"); } Debug.Assert(!SentHeaders, "SentHeaders is true."); if (StatusCode == (int)HttpStatusCode.Unauthorized) { // User set 401 // Using the configured Auth schemes, populate the auth challenge headers. This is for scenarios where // Anonymous access is allowed for some resources, but the server later determines that authorization // is required for this request. HttpListenerContext.SetAuthenticationHeaders(); } // Log headers if (NetEventSource.IsEnabled) { StringBuilder sb = new StringBuilder("HttpListenerResponse Headers:\n"); for (int i = 0; i < Headers.Count; i++) { sb.Append("\t"); sb.Append(Headers.GetKey(i)); sb.Append(" : "); sb.Append(Headers.Get(i)); sb.Append("\n"); } if (NetEventSource.IsEnabled) { NetEventSource.Info(this, sb.ToString()); } } _responseState = ResponseState.SentHeaders; uint statusCode; uint bytesSent; List <GCHandle> pinnedHeaders = SerializeHeaders(ref _nativeResponse.Headers, isWebSocketHandshake); try { if (pDataChunk != null) { _nativeResponse.EntityChunkCount = 1; _nativeResponse.pEntityChunks = pDataChunk; } else if (asyncResult != null && asyncResult.pDataChunks != null) { _nativeResponse.EntityChunkCount = asyncResult.dataChunkCount; _nativeResponse.pEntityChunks = asyncResult.pDataChunks; } else { _nativeResponse.EntityChunkCount = 0; _nativeResponse.pEntityChunks = null; } if (NetEventSource.IsEnabled) { NetEventSource.Info(this, "Calling Interop.HttpApi.HttpSendHttpResponse flags:" + flags); } if (StatusDescription.Length > 0) { byte[] statusDescriptionBytes = new byte[WebHeaderEncoding.GetByteCount(StatusDescription)]; fixed(byte *pStatusDescription = statusDescriptionBytes) { _nativeResponse.ReasonLength = (ushort)statusDescriptionBytes.Length; WebHeaderEncoding.GetBytes(StatusDescription, 0, statusDescriptionBytes.Length, statusDescriptionBytes, 0); _nativeResponse.pReason = (sbyte *)pStatusDescription; fixed(Interop.HttpApi.HTTP_RESPONSE *pResponse = &_nativeResponse) { statusCode = Interop.HttpApi.HttpSendHttpResponse( HttpListenerContext.RequestQueueHandle, HttpListenerRequest.RequestId, (uint)flags, pResponse, null, &bytesSent, SafeLocalAllocHandle.Zero, 0, asyncResult == null ? null : asyncResult._pOverlapped, null); if (asyncResult != null && statusCode == Interop.HttpApi.ERROR_SUCCESS && HttpListener.SkipIOCPCallbackOnSuccess) { asyncResult.IOCompleted(statusCode, bytesSent); // IO operation completed synchronously - callback won't be called to signal completion. } } } } else { fixed(Interop.HttpApi.HTTP_RESPONSE *pResponse = &_nativeResponse) { statusCode = Interop.HttpApi.HttpSendHttpResponse( HttpListenerContext.RequestQueueHandle, HttpListenerRequest.RequestId, (uint)flags, pResponse, null, &bytesSent, SafeLocalAllocHandle.Zero, 0, asyncResult == null ? null : asyncResult._pOverlapped, null); if (asyncResult != null && statusCode == Interop.HttpApi.ERROR_SUCCESS && HttpListener.SkipIOCPCallbackOnSuccess) { asyncResult.IOCompleted(statusCode, bytesSent); // IO operation completed synchronously - callback won't be called to signal completion. } } } if (NetEventSource.IsEnabled) { NetEventSource.Info(this, "Call to Interop.HttpApi.HttpSendHttpResponse returned:" + statusCode); } } finally { FreePinnedHeaders(pinnedHeaders); } return(statusCode); }
private static void IOCompleted(ListenerAsyncResult asyncResult, uint errorCode, uint numBytes) { object result = null; try { if (NetEventSource.IsEnabled) { NetEventSource.Info(null, $"errorCode:[{errorCode}] numBytes:[{numBytes}]"); } if (errorCode != Interop.HttpApi.ERROR_SUCCESS && errorCode != Interop.HttpApi.ERROR_MORE_DATA) { asyncResult.ErrorCode = (int)errorCode; result = new HttpListenerException((int)errorCode); } else { HttpListener httpWebListener = asyncResult.AsyncObject as HttpListener; if (errorCode == Interop.HttpApi.ERROR_SUCCESS) { // at this point we have received an unmanaged HTTP_REQUEST and memoryBlob // points to it we need to hook up our authentication handling code here. bool stoleBlob = false; try { if (httpWebListener.ValidateRequest(asyncResult._requestContext)) { result = httpWebListener.HandleAuthentication(asyncResult._requestContext, out stoleBlob); } } finally { if (stoleBlob) { // The request has been handed to the user, which means this code can't reuse the blob. Reset it here. asyncResult._requestContext = result == null ? new AsyncRequestContext(httpWebListener.RequestQueueBoundHandle, asyncResult) : null; } else { asyncResult._requestContext.Reset(httpWebListener.RequestQueueBoundHandle, 0, 0); } } } else { asyncResult._requestContext.Reset(httpWebListener.RequestQueueBoundHandle, asyncResult._requestContext.RequestBlob->RequestId, numBytes); } // We need to issue a new request, either because auth failed, or because our buffer was too small the first time. if (result == null) { uint statusCode = asyncResult.QueueBeginGetContext(); if (statusCode != Interop.HttpApi.ERROR_SUCCESS && statusCode != Interop.HttpApi.ERROR_IO_PENDING) { // someother bad error, possible return values are: // ERROR_INVALID_HANDLE, ERROR_INSUFFICIENT_BUFFER, ERROR_OPERATION_ABORTED result = new HttpListenerException((int)statusCode); } } if (result == null) { return; } } // complete the async IO and invoke the callback if (NetEventSource.IsEnabled) { NetEventSource.Info(null, "Calling Complete()"); } } catch (Exception exception) when(!ExceptionCheck.IsFatal(exception)) { if (NetEventSource.IsEnabled) { NetEventSource.Info(null, $"Caught exception: {exception}"); } result = exception; } asyncResult.InvokeCallback(result); }
private List <GCHandle> SerializeHeaders(ref Interop.HttpApi.HTTP_RESPONSE_HEADERS headers, bool isWebSocketHandshake) { Interop.HttpApi.HTTP_UNKNOWN_HEADER[] unknownHeaders = null; List <GCHandle> pinnedHeaders; GCHandle gcHandle; if (NetEventSource.IsEnabled) { NetEventSource.Info(this, "SerializeHeaders(HTTP_RESPONSE_HEADERS)"); } if (Headers.Count == 0) { return(null); } string headerName; string headerValue; int lookup; byte[] bytes = null; pinnedHeaders = new List <GCHandle>(); //--------------------------------------------------- // The Set-Cookie headers are being merged into one. // There are two issues here. // 1. When Set-Cookie headers are set through SetCookie method on the ListenerResponse, // there is code in the SetCookie method and the methods it calls to flatten the Set-Cookie // values. This blindly concatenates the cookies with a comma delimiter. There could be // a cookie value that contains comma, but we don't escape it with %XX value // // As an alternative users can add the Set-Cookie header through the AddHeader method // like ListenerResponse.Headers.Add("name", "value") // That way they can add multiple headers - AND They can format the value like they want it. // // 2. Now that the header collection contains multiple Set-Cookie name, value pairs // you would think the problem would go away. However here is an interesting thing. // For NameValueCollection, when you add // "Set-Cookie", "value1" // "Set-Cookie", "value2" // The NameValueCollection.Count == 1. Because there is only one key // NameValueCollection.Get("Set-Cookie") would conviniently take these two valuess // concatenate them with a comma like // value1,value2. // In order to get individual values, you need to use // string[] values = NameValueCollection.GetValues("Set-Cookie"); // // ------------------------------------------------------------- // So here is the proposed fix here. // We must first to loop through all the NameValueCollection keys // and if the name is a unknown header, we must compute the number of // values it has. Then, we should allocate that many unknown header array // elements. // // Note that a part of the fix here is to treat Set-Cookie as an unknown header // // //----------------------------------------------------------- int numUnknownHeaders = 0; for (int index = 0; index < Headers.Count; index++) { headerName = Headers.GetKey(index) as string; //See if this is an unknown header lookup = Interop.HttpApi.HTTP_RESPONSE_HEADER_ID.IndexOfKnownHeader(headerName); //Treat Set-Cookie as well as Connection header in Websocket mode as unknown if (lookup == (int)HttpResponseHeader.SetCookie || isWebSocketHandshake && lookup == (int)HttpResponseHeader.Connection) { lookup = -1; } if (lookup == -1) { string[] headerValues = Headers.GetValues(index); numUnknownHeaders += headerValues.Length; } } try { fixed(Interop.HttpApi.HTTP_KNOWN_HEADER *pKnownHeaders = &headers.KnownHeaders) { for (int index = 0; index < Headers.Count; index++) { headerName = Headers.GetKey(index) as string; headerValue = Headers.Get(index) as string; lookup = Interop.HttpApi.HTTP_RESPONSE_HEADER_ID.IndexOfKnownHeader(headerName); if (lookup == (int)HttpResponseHeader.SetCookie || isWebSocketHandshake && lookup == (int)HttpResponseHeader.Connection) { lookup = -1; } if (NetEventSource.IsEnabled) { NetEventSource.Info(this, $"index={index},headers.count={Headers.Count},headerName:{headerName},lookup:{lookup} headerValue:{headerValue}"); } if (lookup == -1) { if (unknownHeaders == null) { unknownHeaders = new Interop.HttpApi.HTTP_UNKNOWN_HEADER[numUnknownHeaders]; gcHandle = GCHandle.Alloc(unknownHeaders, GCHandleType.Pinned); pinnedHeaders.Add(gcHandle); headers.pUnknownHeaders = (Interop.HttpApi.HTTP_UNKNOWN_HEADER *)gcHandle.AddrOfPinnedObject(); } //---------------------------------------- //FOR UNKNOWN HEADERS //ALLOW MULTIPLE HEADERS to be added //--------------------------------------- string[] headerValues = Headers.GetValues(index); for (int headerValueIndex = 0; headerValueIndex < headerValues.Length; headerValueIndex++) { //Add Name bytes = new byte[WebHeaderEncoding.GetByteCount(headerName)]; unknownHeaders[headers.UnknownHeaderCount].NameLength = (ushort)bytes.Length; WebHeaderEncoding.GetBytes(headerName, 0, bytes.Length, bytes, 0); gcHandle = GCHandle.Alloc(bytes, GCHandleType.Pinned); pinnedHeaders.Add(gcHandle); unknownHeaders[headers.UnknownHeaderCount].pName = (sbyte *)gcHandle.AddrOfPinnedObject(); //Add Value headerValue = headerValues[headerValueIndex]; bytes = new byte[WebHeaderEncoding.GetByteCount(headerValue)]; unknownHeaders[headers.UnknownHeaderCount].RawValueLength = (ushort)bytes.Length; WebHeaderEncoding.GetBytes(headerValue, 0, bytes.Length, bytes, 0); gcHandle = GCHandle.Alloc(bytes, GCHandleType.Pinned); pinnedHeaders.Add(gcHandle); unknownHeaders[headers.UnknownHeaderCount].pRawValue = (sbyte *)gcHandle.AddrOfPinnedObject(); headers.UnknownHeaderCount++; if (NetEventSource.IsEnabled) { NetEventSource.Info(this, "UnknownHeaderCount:" + headers.UnknownHeaderCount); } } } else { if (NetEventSource.IsEnabled) { NetEventSource.Info(this, $"HttpResponseHeader[{lookup}]:{((HttpResponseHeader)lookup)} headerValue:{headerValue}"); } if (headerValue != null) { bytes = new byte[WebHeaderEncoding.GetByteCount(headerValue)]; pKnownHeaders[lookup].RawValueLength = (ushort)bytes.Length; WebHeaderEncoding.GetBytes(headerValue, 0, bytes.Length, bytes, 0); gcHandle = GCHandle.Alloc(bytes, GCHandleType.Pinned); pinnedHeaders.Add(gcHandle); pKnownHeaders[lookup].pRawValue = (sbyte *)gcHandle.AddrOfPinnedObject(); if (NetEventSource.IsEnabled) { NetEventSource.Info(this, $"pRawValue:{((IntPtr)(pKnownHeaders[lookup].pRawValue))} RawValueLength:{pKnownHeaders[lookup].RawValueLength} lookup: {lookup}"); } } } } } } catch { FreePinnedHeaders(pinnedHeaders); throw; } return(pinnedHeaders); }
private object WaitForCompletion(bool snap) { ManualResetEvent waitHandle = null; bool createdByMe = false; bool complete = snap ? IsCompleted : InternalPeekCompleted; if (!complete) { // Not done yet, so wait: waitHandle = (ManualResetEvent)_event; if (waitHandle == null) { createdByMe = LazilyCreateEvent(out waitHandle); } } if (waitHandle != null) { try { if (NetEventSource.IsEnabled) { NetEventSource.Info(this, $"Waiting for completion event {waitHandle}"); } waitHandle.WaitOne(Timeout.Infinite); } catch (ObjectDisposedException) { // This can occur if this method is called from two different threads. // This possibility is the trade-off for not locking. } finally { // We also want to dispose the event although we can't unless we did wait on it here. if (createdByMe && !_userEvent) { // Does _userEvent need to be volatile (or _event set via Interlocked) in order // to avoid giving a user a disposed event? ManualResetEvent oldEvent = (ManualResetEvent)_event; _event = null; if (!_userEvent) { oldEvent.Dispose(); } } } } // A race condition exists because InvokeCallback sets _intCompleted before _result (so that _result // can benefit from the synchronization of _intCompleted). That means you can get here before _result got // set (although rarely - once every eight hours of stress). Handle that case with a spin-lock. SpinWait sw = default; while (_result == DBNull.Value) { sw.SpinOnce(); } if (NetEventSource.IsEnabled) { NetEventSource.Exit(this, _result); } return(_result); }
public IAsyncResult BeginReadCore(byte[] buffer, int offset, int size, AsyncCallback callback, object state) { if (size == 0 || _closed) { if (NetEventSource.Log.IsEnabled()) { NetEventSource.Exit(this); } HttpRequestStreamAsyncResult result = new HttpRequestStreamAsyncResult(this, state, callback); result.InvokeCallback((uint)0); return(result); } HttpRequestStreamAsyncResult asyncResult = null; uint dataRead = 0; if (_dataChunkIndex != -1) { dataRead = Interop.HttpApi.GetChunks(_httpContext.Request.RequestBuffer, _httpContext.Request.OriginalBlobAddress, ref _dataChunkIndex, ref _dataChunkOffset, buffer, offset, size); if (_dataChunkIndex != -1 && dataRead == size) { asyncResult = new HttpRequestStreamAsyncResult(_httpContext.RequestQueueBoundHandle, this, state, callback, buffer, offset, (uint)size, 0); asyncResult.InvokeCallback(dataRead); } } if (_dataChunkIndex == -1 && dataRead < size) { if (NetEventSource.Log.IsEnabled()) { NetEventSource.Info(this, "size:" + size + " offset:" + offset); } uint statusCode = 0; offset += (int)dataRead; size -= (int)dataRead; //the http.sys team recommends that we limit the size to 128kb if (size > MaxReadSize) { size = MaxReadSize; } asyncResult = new HttpRequestStreamAsyncResult(_httpContext.RequestQueueBoundHandle, this, state, callback, buffer, offset, (uint)size, dataRead); uint bytesReturned; try { fixed(byte *pBuffer = buffer) { // issue unmanaged blocking call if (NetEventSource.Log.IsEnabled()) { NetEventSource.Info(this, "Calling Interop.HttpApi.HttpReceiveRequestEntityBody"); } uint flags = 0; if (!_inOpaqueMode) { flags = (uint)Interop.HttpApi.HTTP_FLAGS.HTTP_RECEIVE_REQUEST_FLAG_COPY_BODY; } statusCode = Interop.HttpApi.HttpReceiveRequestEntityBody( _httpContext.RequestQueueHandle, _httpContext.RequestId, flags, asyncResult._pPinnedBuffer, (uint)size, out bytesReturned, asyncResult._pOverlapped); if (NetEventSource.Log.IsEnabled()) { NetEventSource.Info(this, "Call to Interop.HttpApi.HttpReceiveRequestEntityBody returned:" + statusCode + " dataRead:" + dataRead); } } } catch (Exception e) { if (NetEventSource.Log.IsEnabled()) { NetEventSource.Error(this, e.ToString()); } asyncResult.InternalCleanup(); throw; } if (statusCode != Interop.HttpApi.ERROR_SUCCESS && statusCode != Interop.HttpApi.ERROR_IO_PENDING) { asyncResult.InternalCleanup(); if (statusCode == Interop.HttpApi.ERROR_HANDLE_EOF) { asyncResult = new HttpRequestStreamAsyncResult(this, state, callback, dataRead); asyncResult.InvokeCallback((uint)0); } else { Exception exception = new HttpListenerException((int)statusCode); if (NetEventSource.Log.IsEnabled()) { NetEventSource.Error(this, exception.ToString()); } asyncResult.InternalCleanup(); throw exception; } } else if (statusCode == Interop.HttpApi.ERROR_SUCCESS && HttpListener.SkipIOCPCallbackOnSuccess) { // IO operation completed synchronously - callback won't be called to signal completion. asyncResult.IOCompleted(statusCode, bytesReturned); } } if (NetEventSource.Log.IsEnabled()) { NetEventSource.Exit(this); } return(asyncResult); }
/*++ * * Routine Description: * * Takes a native pointer (expressed as an int) to a hostent structure, * and converts the information in their to an IPHostEntry class. This * involves walking through an array of native pointers, and a temporary * ArrayList object is used in doing this. * * Arguments: * * nativePointer - Native pointer to hostent structure. * * * * Return Value: * * An IPHostEntry structure. * * --*/ private static IPHostEntry NativeToHostEntry(IntPtr nativePointer) { // // marshal pointer to struct // hostent Host = Marshal.PtrToStructure <hostent>(nativePointer); IPHostEntry HostEntry = new IPHostEntry(); if (Host.h_name != IntPtr.Zero) { HostEntry.HostName = Marshal.PtrToStringAnsi(Host.h_name); if (NetEventSource.IsEnabled) { NetEventSource.Info(null, $"HostEntry.HostName: {HostEntry.HostName}"); } } // decode h_addr_list to ArrayList of IP addresses. // The h_addr_list field is really a pointer to an array of pointers // to IP addresses. Loop through the array, and while the pointer // isn't NULL read the IP address, convert it to an IPAddress class, // and add it to the list. var TempIPAddressList = new List <IPAddress>(); int IPAddressToAdd; string AliasToAdd; IntPtr currentArrayElement; // // get the first pointer in the array // currentArrayElement = Host.h_addr_list; nativePointer = Marshal.ReadIntPtr(currentArrayElement); while (nativePointer != IntPtr.Zero) { // // if it's not null it points to an IPAddress, // read it... // IPAddressToAdd = Marshal.ReadInt32(nativePointer); #if BIGENDIAN // IP addresses from native code are always a byte array // converted to int. We need to convert the address into // a uniform integer value. IPAddressToAdd = (int)(((uint)IPAddressToAdd << 24) | (((uint)IPAddressToAdd & 0x0000FF00) << 8) | (((uint)IPAddressToAdd >> 8) & 0x0000FF00) | ((uint)IPAddressToAdd >> 24)); #endif if (NetEventSource.IsEnabled) { NetEventSource.Info(null, $"currentArrayElement:{currentArrayElement} nativePointer:{nativePointer} IPAddressToAdd:{IPAddressToAdd}"); } // // ...and add it to the list // TempIPAddressList.Add(new IPAddress((long)IPAddressToAdd & 0x0FFFFFFFF)); // // now get the next pointer in the array and start over // currentArrayElement = currentArrayElement + IntPtr.Size; nativePointer = Marshal.ReadIntPtr(currentArrayElement); } HostEntry.AddressList = TempIPAddressList.ToArray(); // // Now do the same thing for the aliases. // var TempAliasList = new List <string>(); currentArrayElement = Host.h_aliases; nativePointer = Marshal.ReadIntPtr(currentArrayElement); while (nativePointer != IntPtr.Zero) { if (NetEventSource.IsEnabled) { NetEventSource.Info(null, $"currentArrayElement:{currentArrayElement} nativePointer:{nativePointer}"); } // // if it's not null it points to an Alias, // read it... // AliasToAdd = Marshal.PtrToStringAnsi(nativePointer); // // ...and add it to the list // TempAliasList.Add(AliasToAdd); // // now get the next pointer in the array and start over // currentArrayElement = currentArrayElement + IntPtr.Size; nativePointer = Marshal.ReadIntPtr(currentArrayElement); } HostEntry.Aliases = TempAliasList.ToArray(); return(HostEntry); } // NativeToHostEntry
internal CookieCollection CookieCutter(Uri uri, string headerName, string setCookieHeader, bool isThrow) { if (NetEventSource.IsEnabled) { if (NetEventSource.IsEnabled) { NetEventSource.Info(this, $"uri:{uri} headerName:{headerName} setCookieHeader:{setCookieHeader} isThrow:{isThrow}"); } } CookieCollection cookies = new CookieCollection(); CookieVariant variant = CookieVariant.Unknown; if (headerName == null) { variant = CookieVariant.Default; } else { for (int i = 0; i < s_headerInfo.Length; ++i) { if ((string.Equals(headerName, s_headerInfo[i].Name, StringComparison.OrdinalIgnoreCase))) { variant = s_headerInfo[i].Variant; } } } bool isLocalDomain = IsLocalDomain(uri.Host); try { CookieParser parser = new CookieParser(setCookieHeader); do { Cookie cookie = parser.Get(); if (NetEventSource.IsEnabled) { NetEventSource.Info(this, $"CookieParser returned cookie:{cookie}"); } if (cookie == null) { if (parser.EndofHeader()) { break; } continue; } // Parser marks invalid cookies this way if (string.IsNullOrEmpty(cookie.Name)) { if (isThrow) { throw new CookieException(SR.net_cookie_format); } // Otherwise, ignore (reject) cookie continue; } // This will set the default values from the response URI // AND will check for cookie validity if (!cookie.VerifySetDefaults(variant, uri, isLocalDomain, m_fqdnMyDomain, true, isThrow)) { continue; } // If many same cookies arrive we collapse them into just one, hence setting // parameter isStrict = true below cookies.InternalAdd(cookie, true); } while (true); } catch (OutOfMemoryException) { throw; } catch (Exception e) { if (isThrow) { throw new CookieException(SR.Format(SR.net_cookie_parse_header, uri.AbsoluteUri), e); } } foreach (Cookie c in cookies) { Add(c, isThrow); } return(cookies); }
} // GetHostByAddress // Does internal IPAddress reverse and then forward lookups (for Legacy and current public methods). private static IPHostEntry InternalGetHostByAddress(IPAddress address, bool includeIPv6) { if (NetEventSource.IsEnabled) { NetEventSource.Info(null, address); } // // IPv6 Changes: We need to use the new getnameinfo / getaddrinfo functions // for resolution of IPv6 addresses. // if (SocketProtocolSupportPal.OSSupportsIPv6 || includeIPv6) { // // Try to get the data for the host from it's address // // We need to call getnameinfo first, because getaddrinfo w/ the ipaddress string // will only return that address and not the full list. // Do a reverse lookup to get the host name. SocketError errorCode; int nativeErrorCode; string name = NameResolutionPal.TryGetNameInfo(address, out errorCode, out nativeErrorCode); if (errorCode == SocketError.Success) { // Do the forward lookup to get the IPs for that host name IPHostEntry hostEntry; errorCode = NameResolutionPal.TryGetAddrInfo(name, out hostEntry, out nativeErrorCode); if (errorCode == SocketError.Success) { return(hostEntry); } if (NetEventSource.IsEnabled) { NetEventSource.Error(null, SocketExceptionFactory.CreateSocketException(errorCode, nativeErrorCode)); } // One of two things happened: // 1. There was a ptr record in dns, but not a corollary A/AAA record. // 2. The IP was a local (non-loopback) IP that resolved to a connection specific dns suffix. // - Workaround, Check "Use this connection's dns suffix in dns registration" on that network // adapter's advanced dns settings. // Just return the resolved host name and no IPs. return(hostEntry); } throw SocketExceptionFactory.CreateSocketException(errorCode, nativeErrorCode); } // // If IPv6 is not enabled (maybe config switch) but we've been // given an IPv6 address then we need to bail out now. // else { if (address.AddressFamily == AddressFamily.InterNetworkV6) { // // Protocol not supported // throw new SocketException((int)SocketError.ProtocolNotSupported); } // // Use gethostbyaddr() to try to resolve the IP address // // End IPv6 Changes // return(NameResolutionPal.GetHostByAddr(address)); } } // InternalGetHostByAddress