/* * 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. */ // TODO: Consider using HTTP_SEND_RESPONSE_RAW_HEADER with HttpSendResponseEntityBody instead of calling HttpSendHttpResponse. // This will give us more control of the bytes that hit the wire, including encodings, HTTP 1.0, etc.. // It may also be faster to do this work in managed code and then pass down only one buffer. // What would we loose by bypassing HttpSendHttpResponse? // // TODO: Consider using the HTTP_SEND_RESPONSE_FLAG_BUFFER_DATA flag for most/all responses rather than just Opaque. internal unsafe uint SendHeaders(HttpApiTypes.HTTP_DATA_CHUNK[] dataChunks, ResponseStreamAsyncResult asyncResult, HttpApiTypes.HTTP_FLAGS flags, bool isOpaqueUpgrade) { Debug.Assert(!HasStarted, "HttpListenerResponse::SendHeaders()|SentHeaders is true."); _responseState = ResponseState.Started; var reasonPhrase = GetReasonPhrase(StatusCode); uint statusCode; uint bytesSent; List <GCHandle> pinnedHeaders = SerializeHeaders(isOpaqueUpgrade); try { if (dataChunks != null) { if (pinnedHeaders == null) { pinnedHeaders = new List <GCHandle>(); } var handle = GCHandle.Alloc(dataChunks, GCHandleType.Pinned); pinnedHeaders.Add(handle); _nativeResponse.Response_V1.EntityChunkCount = (ushort)dataChunks.Length; _nativeResponse.Response_V1.pEntityChunks = (HttpApiTypes.HTTP_DATA_CHUNK *)handle.AddrOfPinnedObject(); } else if (asyncResult != null && asyncResult.DataChunks != null) { _nativeResponse.Response_V1.EntityChunkCount = asyncResult.DataChunkCount; _nativeResponse.Response_V1.pEntityChunks = asyncResult.DataChunks; } else { _nativeResponse.Response_V1.EntityChunkCount = 0; _nativeResponse.Response_V1.pEntityChunks = null; } var cachePolicy = new HttpApiTypes.HTTP_CACHE_POLICY(); if (_cacheTtl.HasValue && _cacheTtl.Value > TimeSpan.Zero) { cachePolicy.Policy = HttpApiTypes.HTTP_CACHE_POLICY_TYPE.HttpCachePolicyTimeToLive; cachePolicy.SecondsToLive = (uint)Math.Min(_cacheTtl.Value.Ticks / TimeSpan.TicksPerSecond, Int32.MaxValue); } byte[] reasonPhraseBytes = HeaderEncoding.GetBytes(reasonPhrase); fixed(byte *pReasonPhrase = reasonPhraseBytes) { _nativeResponse.Response_V1.ReasonLength = (ushort)reasonPhraseBytes.Length; _nativeResponse.Response_V1.pReason = (byte *)pReasonPhrase; fixed(HttpApiTypes.HTTP_RESPONSE_V2 *pResponse = &_nativeResponse) { statusCode = HttpApi.HttpSendHttpResponse( RequestContext.Server.RequestQueue.Handle, Request.RequestId, (uint)flags, pResponse, &cachePolicy, &bytesSent, IntPtr.Zero, 0, asyncResult == null ? SafeNativeOverlapped.Zero : asyncResult.NativeOverlapped, IntPtr.Zero); // GoAway is only supported on later versions. Retry. if (statusCode == ErrorCodes.ERROR_INVALID_PARAMETER && (flags & HttpApiTypes.HTTP_FLAGS.HTTP_SEND_RESPONSE_FLAG_GOAWAY) != 0) { flags &= ~HttpApiTypes.HTTP_FLAGS.HTTP_SEND_RESPONSE_FLAG_GOAWAY; statusCode = HttpApi.HttpSendHttpResponse( RequestContext.Server.RequestQueue.Handle, Request.RequestId, (uint)flags, pResponse, &cachePolicy, &bytesSent, IntPtr.Zero, 0, asyncResult == null ? SafeNativeOverlapped.Zero : asyncResult.NativeOverlapped, IntPtr.Zero); // Succeeded without GoAway, disable them. if (statusCode != ErrorCodes.ERROR_INVALID_PARAMETER) { SupportsGoAway = false; } } if (asyncResult != null && statusCode == ErrorCodes.ERROR_SUCCESS && HttpSysListener.SkipIOCPCallbackOnSuccess) { asyncResult.BytesSent = bytesSent; // The caller will invoke IOCompleted } } } } finally { FreePinnedHeaders(pinnedHeaders); } return(statusCode); }
internal unsafe Task SendFileAsyncCore(string fileName, long offset, long?count, CancellationToken cancellationToken) { if (_skipWrites) { return(Task.CompletedTask); } var started = _requestContext.Response.HasStarted; if (count == 0 && started) { // No data to send and we've already sent the headers return(Task.CompletedTask); } if (cancellationToken.IsCancellationRequested) { Abort(ThrowWriteExceptions); return(Task.FromCanceled <int>(cancellationToken)); } // We are setting buffer size to 1 to prevent FileStream from allocating it's internal buffer // It's too expensive to validate anything before opening the file. Open the file and then check the lengths. var fileStream = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, bufferSize: 1, options: FileOptions.Asynchronous | FileOptions.SequentialScan); // Extremely expensive. try { var length = fileStream.Length; // Expensive, only do it once if (!count.HasValue) { count = length - offset; } if (offset < 0 || offset > length) { throw new ArgumentOutOfRangeException(nameof(offset), offset, string.Empty); } if (count < 0 || count > length - offset) { throw new ArgumentOutOfRangeException(nameof(count), count, string.Empty); } CheckWriteCount(count); } catch { fileStream.Dispose(); throw; } // Make sure all validation is performed before this computes the headers var flags = ComputeLeftToWrite(count.Value); uint statusCode; uint bytesSent = 0; var chunked = _requestContext.Response.BoundaryType == BoundaryType.Chunked; var asyncResult = new ResponseStreamAsyncResult(this, fileStream, offset, count.Value, chunked, cancellationToken); try { if (!started) { statusCode = _requestContext.Response.SendHeaders(null, asyncResult, flags, false); bytesSent = asyncResult.BytesSent; } else { // TODO: If opaque then include the buffer data flag. statusCode = HttpApi.HttpSendResponseEntityBody( RequestQueueHandle, RequestId, (uint)flags, asyncResult.DataChunkCount, asyncResult.DataChunks, &bytesSent, IntPtr.Zero, 0, asyncResult.NativeOverlapped, IntPtr.Zero); } } catch (Exception e) { Logger.LogError(LoggerEventIds.FileSendAsyncError, e, "SendFileAsync"); asyncResult.Dispose(); Abort(); throw; } if (statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS && statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_IO_PENDING) { if (cancellationToken.IsCancellationRequested) { Logger.LogDebug(LoggerEventIds.FileSendAsyncCancelled, $"SendFileAsync; Write cancelled with error code: {statusCode}"); asyncResult.Cancel(ThrowWriteExceptions); } else if (ThrowWriteExceptions) { asyncResult.Dispose(); var exception = new IOException(string.Empty, new HttpSysException((int)statusCode)); Logger.LogError(LoggerEventIds.FileSendAsyncError, exception, "SendFileAsync"); Abort(); throw exception; } else { // Abort the request but do not close the stream, let future writes complete silently Logger.LogDebug(LoggerEventIds.FileSendAsyncErrorIgnored, $"SendFileAsync; Ignored write exception: {statusCode}"); asyncResult.FailSilently(); } } if (statusCode == ErrorCodes.ERROR_SUCCESS && HttpSysListener.SkipIOCPCallbackOnSuccess) { // IO operation completed synchronously - callback won't be called to signal completion. asyncResult.IOCompleted(statusCode, bytesSent); } // Last write, cache it for special cancellation handling. if ((flags & HttpApiTypes.HTTP_FLAGS.HTTP_SEND_RESPONSE_FLAG_MORE_DATA) == 0) { _lastWrite = asyncResult; } return(asyncResult.Task); }
private unsafe CancellationToken CreateDisconnectToken(ulong connectionId) { LogHelper.LogDebug(_logger, "CreateDisconnectToken", "Registering connection for disconnect for connection ID: " + connectionId); // Create a nativeOverlapped callback so we can register for disconnect callback var cts = new CancellationTokenSource(); var returnToken = cts.Token; SafeNativeOverlapped nativeOverlapped = null; var boundHandle = _requestQueue.BoundHandle; nativeOverlapped = new SafeNativeOverlapped(boundHandle, boundHandle.AllocateNativeOverlapped( (errorCode, numBytes, overlappedPtr) => { LogHelper.LogDebug(_logger, "CreateDisconnectToken", "http.sys disconnect callback fired for connection ID: " + connectionId); // Free the overlapped nativeOverlapped.Dispose(); // Pull the token out of the list and Cancel it. ConnectionCancellation token; _connectionCancellationTokens.TryRemove(connectionId, out token); try { cts.Cancel(); } catch (AggregateException exception) { LogHelper.LogException(_logger, "CreateDisconnectToken Callback", exception); } }, null, null)); uint statusCode; try { statusCode = HttpApi.HttpWaitForDisconnectEx(requestQueueHandle: _requestQueue.Handle, connectionId: connectionId, reserved: 0, overlapped: nativeOverlapped); } catch (Win32Exception exception) { statusCode = (uint)exception.NativeErrorCode; LogHelper.LogException(_logger, "CreateDisconnectToken", exception); } if (statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_IO_PENDING && statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS) { // We got an unknown result, assume the connection has been closed. nativeOverlapped.Dispose(); ConnectionCancellation ignored; _connectionCancellationTokens.TryRemove(connectionId, out ignored); LogHelper.LogDebug(_logger, "HttpWaitForDisconnectEx", new Win32Exception((int)statusCode)); cts.Cancel(); } if (statusCode == UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS && HttpSysListener.SkipIOCPCallbackOnSuccess) { // IO operation completed synchronously - callback won't be called to signal completion nativeOverlapped.Dispose(); ConnectionCancellation ignored; _connectionCancellationTokens.TryRemove(connectionId, out ignored); cts.Cancel(); } return(returnToken); }
// Simpler than Flush because it will never be called at the end of the request from Dispose. private unsafe Task FlushInternalAsync(ArraySegment <byte> data, CancellationToken cancellationToken) { if (_skipWrites) { return(Task.CompletedTask); } var started = _requestContext.Response.HasStarted; if (data.Count == 0 && started) { // No data to send and we've already sent the headers return(Task.CompletedTask); } if (cancellationToken.IsCancellationRequested) { Abort(ThrowWriteExceptions); return(Task.FromCanceled <int>(cancellationToken)); } // Make sure all validation is performed before this computes the headers var flags = ComputeLeftToWrite(data.Count); uint statusCode = 0; var chunked = _requestContext.Response.BoundaryType == BoundaryType.Chunked; var asyncResult = new ResponseStreamAsyncResult(this, data, chunked, cancellationToken); uint bytesSent = 0; try { if (!started) { statusCode = _requestContext.Response.SendHeaders(null, asyncResult, flags, false); bytesSent = asyncResult.BytesSent; } else { statusCode = HttpApi.HttpSendResponseEntityBody( RequestQueueHandle, RequestId, (uint)flags, asyncResult.DataChunkCount, asyncResult.DataChunks, &bytesSent, IntPtr.Zero, 0, asyncResult.NativeOverlapped, IntPtr.Zero); } } catch (Exception e) { Logger.LogError(LoggerEventIds.ErrorWhenFlushAsync, e, "FlushAsync"); asyncResult.Dispose(); Abort(); throw; } if (statusCode != ErrorCodes.ERROR_SUCCESS && statusCode != ErrorCodes.ERROR_IO_PENDING) { if (cancellationToken.IsCancellationRequested) { Logger.LogDebug(LoggerEventIds.WriteFlushCancelled, $"FlushAsync; Write cancelled with error code: {statusCode}"); asyncResult.Cancel(ThrowWriteExceptions); } else if (ThrowWriteExceptions) { asyncResult.Dispose(); Exception exception = new IOException(string.Empty, new HttpSysException((int)statusCode)); Logger.LogError(LoggerEventIds.ErrorWhenFlushAsync, exception, "FlushAsync"); Abort(); throw exception; } else { // Abort the request but do not close the stream, let future writes complete silently Logger.LogDebug(LoggerEventIds.WriteErrorIgnored, $"FlushAsync; Ignored write exception: {statusCode}"); asyncResult.FailSilently(); } } if (statusCode == ErrorCodes.ERROR_SUCCESS && HttpSysListener.SkipIOCPCallbackOnSuccess) { // IO operation completed synchronously - callback won't be called to signal completion. asyncResult.IOCompleted(statusCode, bytesSent); } // Last write, cache it for special cancellation handling. if ((flags & HttpApiTypes.HTTP_FLAGS.HTTP_SEND_RESPONSE_FLAG_MORE_DATA) == 0) { _lastWrite = asyncResult; } return(asyncResult.Task); }
// We never expect endOfRequest and data at the same time private unsafe void FlushInternal(bool endOfRequest, ArraySegment <byte> data = new ArraySegment <byte>()) { Debug.Assert(!(endOfRequest && data.Count > 0), "Data is not supported at the end of the request."); if (_skipWrites) { return; } var started = _requestContext.Response.HasStarted; if (data.Count == 0 && started && !endOfRequest) { // No data to send and we've already sent the headers return; } // Make sure all validation is performed before this computes the headers var flags = ComputeLeftToWrite(data.Count, endOfRequest); if (endOfRequest && _leftToWrite > 0) { if (!RequestContext.DisconnectToken.IsCancellationRequested) { // This is logged rather than thrown because it is too late for an exception to be visible in user code. Logger.LogError(LoggerEventIds.FewerBytesThanExpected, "ResponseStream::Dispose; Fewer bytes were written than were specified in the Content-Length."); } _requestContext.Abort(); return; } uint statusCode = 0; HttpApiTypes.HTTP_DATA_CHUNK[] dataChunks; var pinnedBuffers = PinDataBuffers(endOfRequest, data, out dataChunks); try { if (!started) { statusCode = _requestContext.Response.SendHeaders(dataChunks, null, flags, false); } else { fixed(HttpApiTypes.HTTP_DATA_CHUNK *pDataChunks = dataChunks) { statusCode = HttpApi.HttpSendResponseEntityBody( RequestQueueHandle, RequestId, (uint)flags, (ushort)dataChunks.Length, pDataChunks, null, IntPtr.Zero, 0, SafeNativeOverlapped.Zero, IntPtr.Zero); } } } finally { FreeDataBuffers(pinnedBuffers); } if (statusCode != ErrorCodes.ERROR_SUCCESS && statusCode != ErrorCodes.ERROR_HANDLE_EOF // Don't throw for disconnects, we were already finished with the response. && (!endOfRequest || (statusCode != ErrorCodes.ERROR_CONNECTION_INVALID && statusCode != ErrorCodes.ERROR_INVALID_PARAMETER))) { if (ThrowWriteExceptions) { var exception = new IOException(string.Empty, new HttpSysException((int)statusCode)); Logger.LogError(LoggerEventIds.WriteError, exception, "Flush"); Abort(); throw exception; } else { // Abort the request but do not close the stream, let future writes complete silently Logger.LogDebug(LoggerEventIds.WriteErrorIgnored, $"Flush; Ignored write exception: {statusCode}"); Abort(dispose: false); } } }
internal static unsafe ChannelBinding?GetChannelBindingFromTls(RequestQueue requestQueue, ulong connectionId, ILogger logger) { // +128 since a CBT is usually <128 thus we need to call HRCC just once. If the CBT // is >128 we will get ERROR_MORE_DATA and call again int size = RequestChannelBindStatusSize + 128; Debug.Assert(size >= 0); byte[]? blob = null; SafeLocalFreeChannelBinding?token = null; uint bytesReceived = 0;; uint statusCode; do { blob = new byte[size]; fixed(byte *blobPtr = blob) { // Http.sys team: ServiceName will always be null if // HTTP_RECEIVE_SECURE_CHANNEL_TOKEN flag is set. statusCode = HttpApi.HttpReceiveClientCertificate( requestQueue.Handle, connectionId, (uint)HttpApiTypes.HTTP_FLAGS.HTTP_RECEIVE_SECURE_CHANNEL_TOKEN, blobPtr, (uint)size, &bytesReceived, SafeNativeOverlapped.Zero); if (statusCode == UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS) { int tokenOffset = GetTokenOffsetFromBlob((IntPtr)blobPtr); int tokenSize = GetTokenSizeFromBlob((IntPtr)blobPtr); Debug.Assert(tokenSize < Int32.MaxValue); token = SafeLocalFreeChannelBinding.LocalAlloc(tokenSize); Marshal.Copy(blob, tokenOffset, token.DangerousGetHandle(), tokenSize); } else if (statusCode == UnsafeNclNativeMethods.ErrorCodes.ERROR_MORE_DATA) { int tokenSize = GetTokenSizeFromBlob((IntPtr)blobPtr); Debug.Assert(tokenSize < Int32.MaxValue); size = RequestChannelBindStatusSize + tokenSize; } else if (statusCode == UnsafeNclNativeMethods.ErrorCodes.ERROR_INVALID_PARAMETER) { logger.LogError(LoggerEventIds.ChannelBindingUnSupported, "GetChannelBindingFromTls; Channel binding is not supported."); return(null); // old schannel library which doesn't support CBT } else { // It's up to the consumer to fail if the missing ChannelBinding matters to them. logger.LogError(LoggerEventIds.ChannelBindingMissing, new HttpSysException((int)statusCode), "GetChannelBindingFromTls"); break; } } }while (statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS); return(token); }
private static unsafe void IOCompleted(ClientCertLoader asyncResult, uint errorCode, uint numBytes) { RequestContext requestContext = asyncResult.RequestContext; try { if (errorCode == UnsafeNclNativeMethods.ErrorCodes.ERROR_MORE_DATA) { // There is a bug that has existed in http.sys since w2k3. Bytesreceived will only // return the size of the initial cert structure. To get the full size, // we need to add the certificate encoding size as well. HttpApiTypes.HTTP_SSL_CLIENT_CERT_INFO *pClientCertInfo = asyncResult.RequestBlob; asyncResult.Reset(numBytes + pClientCertInfo->CertEncodedSize); uint bytesReceived = 0; errorCode = HttpApi.HttpReceiveClientCertificate( requestContext.Server.RequestQueue.Handle, requestContext.Request.UConnectionId, (uint)HttpApiTypes.HTTP_FLAGS.NONE, asyncResult._memoryBlob, asyncResult._size, &bytesReceived, asyncResult._overlapped !); if (errorCode == UnsafeNclNativeMethods.ErrorCodes.ERROR_IO_PENDING || (errorCode == UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS && !HttpSysListener.SkipIOCPCallbackOnSuccess)) { return; } } if (errorCode == UnsafeNclNativeMethods.ErrorCodes.ERROR_NOT_FOUND) { // The client did not send a cert. asyncResult.Complete(0, null); } else if (errorCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS) { asyncResult.Fail(new HttpSysException((int)errorCode)); } else { HttpApiTypes.HTTP_SSL_CLIENT_CERT_INFO *pClientCertInfo = asyncResult._memoryBlob; if (pClientCertInfo == null) { asyncResult.Complete(0, null); } else { if (pClientCertInfo->pCertEncoded != null) { try { byte[] certEncoded = new byte[pClientCertInfo->CertEncodedSize]; Marshal.Copy((IntPtr)pClientCertInfo->pCertEncoded, certEncoded, 0, certEncoded.Length); asyncResult.Complete((int)pClientCertInfo->CertFlags, new X509Certificate2(certEncoded)); } catch (CryptographicException exception) { // TODO: Log asyncResult.Fail(exception); } catch (SecurityException exception) { // TODO: Log asyncResult.Fail(exception); } } } } } catch (Exception exception) { asyncResult.Fail(exception); } }
internal RequestQueue(UrlGroup urlGroup, string requestQueueName, RequestQueueMode mode, ILogger logger) { _mode = mode; _urlGroup = urlGroup; _logger = logger; var flags = HttpApiTypes.HTTP_CREATE_REQUEST_QUEUE_FLAG.None; Created = true; if (_mode == RequestQueueMode.Attach) { flags = HttpApiTypes.HTTP_CREATE_REQUEST_QUEUE_FLAG.OpenExisting; Created = false; } var statusCode = HttpApi.HttpCreateRequestQueue( HttpApi.Version, requestQueueName, null, flags, out var requestQueueHandle); if (_mode == RequestQueueMode.CreateOrAttach && statusCode == UnsafeNclNativeMethods.ErrorCodes.ERROR_ALREADY_EXISTS) { // Tried to create, but it already exists so attach to it instead. Created = false; flags = HttpApiTypes.HTTP_CREATE_REQUEST_QUEUE_FLAG.OpenExisting; statusCode = HttpApi.HttpCreateRequestQueue( HttpApi.Version, requestQueueName, null, flags, out requestQueueHandle); } if (flags == HttpApiTypes.HTTP_CREATE_REQUEST_QUEUE_FLAG.OpenExisting && statusCode == UnsafeNclNativeMethods.ErrorCodes.ERROR_FILE_NOT_FOUND) { throw new HttpSysException((int)statusCode, $"Failed to attach to the given request queue '{requestQueueName}', the queue could not be found."); } else if (statusCode == UnsafeNclNativeMethods.ErrorCodes.ERROR_INVALID_NAME) { throw new HttpSysException((int)statusCode, $"The given request queue name '{requestQueueName}' is invalid."); } else if (statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS) { throw new HttpSysException((int)statusCode); } // Disabling callbacks when IO operation completes synchronously (returns ErrorCodes.ERROR_SUCCESS) if (HttpSysListener.SkipIOCPCallbackOnSuccess && !UnsafeNclNativeMethods.SetFileCompletionNotificationModes( requestQueueHandle, UnsafeNclNativeMethods.FileCompletionNotificationModes.SkipCompletionPortOnSuccess | UnsafeNclNativeMethods.FileCompletionNotificationModes.SkipSetEventOnHandle)) { requestQueueHandle.Dispose(); throw new HttpSysException(Marshal.GetLastWin32Error()); } Handle = requestQueueHandle; BoundHandle = ThreadPoolBoundHandle.BindHandle(Handle); if (!Created) { _logger.LogInformation(LoggerEventIds.AttachedToQueue, "Attached to an existing request queue '{requestQueueName}', some options do not apply.", requestQueueName); } }