public static void AddResponseCookiesToContainer(WinHttpRequestState state) { HttpRequestMessage request = state.RequestMessage; SafeWinHttpHandle requestHandle = state.RequestHandle; CookieContainer cookieContainer = state.Handler.CookieContainer; Debug.Assert(state.Handler.CookieUsePolicy == CookieUsePolicy.UseSpecifiedCookieContainer); Debug.Assert(cookieContainer != null); // Get 'Set-Cookie' headers from response. char[] buffer = null; uint index = 0; string cookieHeader; WinHttpTraceHelper.Trace("WINHTTP_QUERY_SET_COOKIE"); while (WinHttpResponseParser.GetResponseHeader( requestHandle, Interop.WinHttp.WINHTTP_QUERY_SET_COOKIE, ref buffer, ref index, out cookieHeader)) { WinHttpTraceHelper.Trace(cookieHeader); try { cookieContainer.SetCookies(request.RequestUri, cookieHeader); WinHttpTraceHelper.Trace(cookieHeader); } catch (CookieException) { // We ignore malformed cookies in the response. WinHttpTraceHelper.Trace("Ignoring invalid cookie: {0}", cookieHeader); } } }
private static void OnRequestReadComplete(WinHttpRequestState state, uint bytesRead) { Debug.Assert(state != null, "OnRequestReadComplete: state is null"); Debug.Assert(state.TcsReadFromResponseStream != null, "TcsReadFromResponseStream is null"); Debug.Assert(!state.TcsReadFromResponseStream.Task.IsCompleted, "TcsReadFromResponseStream.Task is completed"); state.DisposeCtrReadFromResponseStream(); // If we read to the end of the stream and we're using 'Content-Length' semantics on the response body, // then verify we read at least the number of bytes required. if (bytesRead == 0 && state.ExpectedBytesToRead.HasValue && state.CurrentBytesRead < state.ExpectedBytesToRead.Value) { state.TcsReadFromResponseStream.TrySetException( new IOException(string.Format( "net_http_io_read_incomplete: {0} - {1}", state.ExpectedBytesToRead.Value, state.CurrentBytesRead))); } else { state.CurrentBytesRead += (long)bytesRead; state.TcsReadFromResponseStream.TrySetResult((int)bytesRead); } }
public static void WinHttpCallback( IntPtr handle, IntPtr context, uint internetStatus, IntPtr statusInformation, uint statusInformationLength) { WinHttpTraceHelper.TraceCallbackStatus("WinHttpCallback", handle, context, internetStatus); if (Environment.HasShutdownStarted) { WinHttpTraceHelper.Trace("WinHttpCallback: Environment.HasShutdownStarted returned True"); return; } if (context == IntPtr.Zero) { return; } WinHttpRequestState state = WinHttpRequestState.FromIntPtr(context); Debug.Assert(state != null, "WinHttpCallback must have a non-null state object"); RequestCallback(handle, state, internetStatus, statusInformation, statusInformationLength); }
private static void OnRequestWriteComplete(WinHttpRequestState state) { Debug.Assert(state != null, "OnRequestWriteComplete: state is null"); Debug.Assert(state.TcsInternalWriteDataToRequestStream != null, "TcsInternalWriteDataToRequestStream is null"); Debug.Assert(!state.TcsInternalWriteDataToRequestStream.Task.IsCompleted, "TcsInternalWriteDataToRequestStream.Task is completed"); state.TcsInternalWriteDataToRequestStream.TrySetResult(true); }
private static void OnRequestReceiveResponseHeadersComplete(WinHttpRequestState state) { Debug.Assert(state != null, "OnRequestReceiveResponseHeadersComplete: state is null"); Debug.Assert(state.TcsReceiveResponseHeaders != null, "TcsReceiveResponseHeaders is null"); Debug.Assert(!state.TcsReceiveResponseHeaders.Task.IsCompleted, "TcsReceiveResponseHeaders.Task is completed"); state.TcsReceiveResponseHeaders.TrySetResult(true); }
private static void OnRequestSendRequestComplete(WinHttpRequestState state) { Debug.Assert(state != null, "OnRequestSendRequestComplete: state is null"); Debug.Assert(state.TcsSendRequest != null, "OnRequestSendRequestComplete: TcsSendRequest is null"); Debug.Assert(!state.TcsSendRequest.Task.IsCompleted, "OnRequestSendRequestComplete: TcsSendRequest.Task is completed"); state.TcsSendRequest.TrySetResult(true); }
private static void OnRequestDataAvailable(WinHttpRequestState state, int bytesAvailable) { Debug.Assert(state != null, "OnRequestDataAvailable: state is null"); Debug.Assert(state.TcsQueryDataAvailable != null, "TcsQueryDataAvailable is null"); Debug.Assert(!state.TcsQueryDataAvailable.Task.IsCompleted, "TcsQueryDataAvailable.Task is completed"); state.TcsQueryDataAvailable.TrySetResult(bytesAvailable); }
private static void OnRequestHandleClosing(WinHttpRequestState state) { Debug.Assert(state != null, "OnRequestSendRequestComplete: state is null"); // This is the last notification callback that WinHTTP will send. Therefore, we can // now explicitly dispose the state object which will free its corresponding GCHandle. // This will then allow the state object to be garbage collected. state.Dispose(); }
public void PreAuthenticateRequest(WinHttpRequestState state, uint proxyAuthScheme) { // Set proxy credentials if we have them. // If a proxy authentication challenge was responded to, reset // those credentials before each SendRequest, because the proxy // may require re-authentication after responding to a 401 or // to a redirect. If you don't, you can get into a // 407-401-407-401- loop. if (proxyAuthScheme != 0) { ICredentials proxyCredentials; Uri proxyUri; if (state.Proxy != null) { proxyCredentials = state.Proxy.Credentials; proxyUri = state.Proxy.GetProxy(state.RequestMessage.RequestUri); } else { proxyCredentials = state.DefaultProxyCredentials; proxyUri = state.RequestMessage.RequestUri; } SetWinHttpCredential( state.RequestHandle, proxyCredentials, proxyUri, proxyAuthScheme, Interop.WinHttp.WINHTTP_AUTH_TARGET_PROXY); } // Apply pre-authentication headers for server authentication? if (state.PreAuthenticate) { uint authScheme; NetworkCredential serverCredentials; if (GetServerCredentialsFromCache( state.RequestMessage.RequestUri, out authScheme, out serverCredentials)) { SetWinHttpCredential( state.RequestHandle, serverCredentials, state.RequestMessage.RequestUri, authScheme, Interop.WinHttp.WINHTTP_AUTH_TARGET_SERVER); state.LastStatusCode = HttpStatusCode.Unauthorized; // Remember we already set the creds. } // No cached credential to use at this time. The request will first go out with no // 'Authorization' header. Later, if a 401 occurs, we will be able to cache the credential // since we will then know the proper auth scheme to use. // // TODO: Issue #2165. Adding logging to highlight the 'cache miss'. } }
private static void OnRequestRedirect(WinHttpRequestState state, Uri redirectUri) { Debug.Assert(state != null, "OnRequestRedirect: state is null"); Debug.Assert(redirectUri != null, "OnRequestRedirect: redirectUri is null"); Debug.Assert(state.TcsReceiveResponseHeaders != null, "TcsReceiveResponseHeaders is null"); Debug.Assert(!state.TcsReceiveResponseHeaders.Task.IsCompleted, "TcsReceiveResponseHeaders.Task is completed"); // If we're manually handling cookies, we need to reset them based on the new URI. if (state.Handler.CookieUsePolicy == CookieUsePolicy.UseSpecifiedCookieContainer) { // Add any cookies that may have arrived with redirect response. WinHttpCookieContainerAdapter.AddResponseCookiesToContainer(state); // Reset cookie request headers based on redirectUri. WinHttpCookieContainerAdapter.ResetCookieRequestHeaders(state, redirectUri); } state.RequestMessage.RequestUri = redirectUri; // Redirection to a new uri may require a new connection through a potentially different proxy. // If so, we will need to respond to additional 407 proxy auth demands and re-attach any // proxy credentials. The ProcessResponse() method looks at the state.LastStatusCode // before attaching proxy credentials and marking the HTTP request to be re-submitted. // So we need to reset the LastStatusCode remembered. Otherwise, it will see additional 407 // responses as an indication that proxy auth failed and won't retry the HTTP request. if (state.LastStatusCode == HttpStatusCode.ProxyAuthenticationRequired) { state.LastStatusCode = 0; } // For security reasons, we drop the server credential if it is a // NetworkCredential. But we allow credentials in a CredentialCache // since they are specifically tied to URI's. if (!(state.ServerCredentials is CredentialCache)) { state.ServerCredentials = null; } }
public static void ResetCookieRequestHeaders(WinHttpRequestState state, Uri redirectUri) { SafeWinHttpHandle requestHandle = state.RequestHandle; Debug.Assert(state.Handler.CookieUsePolicy == CookieUsePolicy.UseSpecifiedCookieContainer); // Clear cookies. if (!Interop.WinHttp.WinHttpAddRequestHeaders( requestHandle, CookieHeaderNameWithColon, (uint)CookieHeaderNameWithColon.Length, Interop.WinHttp.WINHTTP_ADDREQ_FLAG_REPLACE)) { int lastError = Marshal.GetLastWin32Error(); if (lastError != Interop.WinHttp.ERROR_WINHTTP_HEADER_NOT_FOUND) { throw WinHttpException.CreateExceptionUsingError(lastError); } } // Re-add cookies. The GetCookieHeader() method will return the correct set of // cookies based on the redirectUri. string cookieHeader = GetCookieHeader(redirectUri, state.Handler.CookieContainer); if (!string.IsNullOrEmpty(cookieHeader)) { if (!Interop.WinHttp.WinHttpAddRequestHeaders( requestHandle, cookieHeader, (uint)cookieHeader.Length, Interop.WinHttp.WINHTTP_ADDREQ_FLAG_ADD)) { WinHttpException.ThrowExceptionUsingLastError(); } } }
private static void RequestCallback( IntPtr handle, WinHttpRequestState state, uint internetStatus, IntPtr statusInformation, uint statusInformationLength) { try { switch (internetStatus) { case Interop.WinHttp.WINHTTP_CALLBACK_STATUS_HANDLE_CLOSING: OnRequestHandleClosing(state); return; case Interop.WinHttp.WINHTTP_CALLBACK_STATUS_SENDREQUEST_COMPLETE: OnRequestSendRequestComplete(state); return; case Interop.WinHttp.WINHTTP_CALLBACK_STATUS_DATA_AVAILABLE: Debug.Assert(statusInformationLength == Marshal.SizeOf <int>()); int bytesAvailable = Marshal.ReadInt32(statusInformation); OnRequestDataAvailable(state, bytesAvailable); return; case Interop.WinHttp.WINHTTP_CALLBACK_STATUS_READ_COMPLETE: OnRequestReadComplete(state, statusInformationLength); return; case Interop.WinHttp.WINHTTP_CALLBACK_STATUS_WRITE_COMPLETE: OnRequestWriteComplete(state); return; case Interop.WinHttp.WINHTTP_CALLBACK_STATUS_HEADERS_AVAILABLE: OnRequestReceiveResponseHeadersComplete(state); return; case Interop.WinHttp.WINHTTP_CALLBACK_STATUS_REDIRECT: string redirectUriString = Marshal.PtrToStringUni(statusInformation); var redirectUri = new Uri(redirectUriString); OnRequestRedirect(state, redirectUri); return; case Interop.WinHttp.WINHTTP_CALLBACK_STATUS_SENDING_REQUEST: OnRequestSendingRequest(state); return; case Interop.WinHttp.WINHTTP_CALLBACK_STATUS_REQUEST_ERROR: Debug.Assert( statusInformationLength == Marshal.SizeOf <Interop.WinHttp.WINHTTP_ASYNC_RESULT>(), "RequestCallback: statusInformationLength=" + statusInformationLength + " must be sizeof(WINHTTP_ASYNC_RESULT)=" + Marshal.SizeOf <Interop.WinHttp.WINHTTP_ASYNC_RESULT>()); var asyncResult = Marshal.PtrToStructure <Interop.WinHttp.WINHTTP_ASYNC_RESULT>(statusInformation); OnRequestError(state, asyncResult); return; default: return; } } catch (Exception ex) { Interop.WinHttp.WinHttpCloseHandle(handle); state.SavedException = ex; } }
private static void OnRequestError(WinHttpRequestState state, Interop.WinHttp.WINHTTP_ASYNC_RESULT asyncResult) { WinHttpTraceHelper.TraceAsyncError("OnRequestError", asyncResult); Debug.Assert(state != null, "OnRequestError: state is null"); Exception innerException = WinHttpException.CreateExceptionUsingError(unchecked ((int)asyncResult.dwError)); switch (unchecked ((uint)asyncResult.dwResult.ToInt32())) { case Interop.WinHttp.API_SEND_REQUEST: state.TcsSendRequest.TrySetException(innerException); break; case Interop.WinHttp.API_RECEIVE_RESPONSE: if (asyncResult.dwError == Interop.WinHttp.ERROR_WINHTTP_RESEND_REQUEST) { state.RetryRequest = true; state.TcsReceiveResponseHeaders.TrySetResult(false); } else if (asyncResult.dwError == Interop.WinHttp.ERROR_WINHTTP_CLIENT_AUTH_CERT_NEEDED) { // WinHttp will automatically drop any client SSL certificates that we // have pre-set into the request handle including the NULL certificate // (which means we have no certs to send). For security reasons, we don't // allow the certificate to be re-applied. But we need to tell WinHttp // explicitly that we don't have any certificate to send. Debug.Assert(state.RequestHandle != null, "OnRequestError: state.RequestHandle is null"); WinHttpHandler.SetNoClientCertificate(state.RequestHandle); state.RetryRequest = true; state.TcsReceiveResponseHeaders.TrySetResult(false); } else if (asyncResult.dwError == Interop.WinHttp.ERROR_WINHTTP_OPERATION_CANCELLED) { state.TcsReceiveResponseHeaders.TrySetCanceled(state.CancellationToken); } else { state.TcsReceiveResponseHeaders.TrySetException(innerException); } break; case Interop.WinHttp.API_QUERY_DATA_AVAILABLE: if (asyncResult.dwError == Interop.WinHttp.ERROR_WINHTTP_OPERATION_CANCELLED) { // TODO: Issue #2165. We need to pass in the cancellation token from the // user's ReadAsync() call into the TrySetCanceled(). Debug.WriteLine("RequestCallback: QUERY_DATA_AVAILABLE - ERROR_WINHTTP_OPERATION_CANCELLED"); state.TcsQueryDataAvailable.TrySetCanceled(); } else { state.TcsQueryDataAvailable.TrySetException( new IOException("net_http_io_read", innerException)); } break; case Interop.WinHttp.API_READ_DATA: state.DisposeCtrReadFromResponseStream(); if (asyncResult.dwError == Interop.WinHttp.ERROR_WINHTTP_OPERATION_CANCELLED) { // TODO: Issue #2165. We need to pass in the cancellation token from the // user's ReadAsync() call into the TrySetCanceled(). Debug.WriteLine("RequestCallback: API_READ_DATA - ERROR_WINHTTP_OPERATION_CANCELLED"); state.TcsReadFromResponseStream.TrySetCanceled(); } else { state.TcsReadFromResponseStream.TrySetException( new IOException("net_http_io_read", innerException)); } break; case Interop.WinHttp.API_WRITE_DATA: if (asyncResult.dwError == Interop.WinHttp.ERROR_WINHTTP_OPERATION_CANCELLED) { // TODO: Issue #2165. We need to pass in the cancellation token from the // user's WriteAsync() call into the TrySetCanceled(). Debug.WriteLine("RequestCallback: API_WRITE_DATA - ERROR_WINHTTP_OPERATION_CANCELLED"); state.TcsInternalWriteDataToRequestStream.TrySetCanceled(); } else { state.TcsInternalWriteDataToRequestStream.TrySetException( new IOException("net_http_io_write", innerException)); } break; default: Debug.Fail( "OnRequestError: Result (" + asyncResult.dwResult + ") is not expected.", "Error code: " + asyncResult.dwError + " (" + innerException.Message + ")"); break; } }
internal WinHttpRequestStream(WinHttpRequestState state, bool chunkedMode) { _state = state; _chunkedMode = chunkedMode; }
private static void OnRequestSendingRequest(WinHttpRequestState state) { Debug.Assert(state != null, "OnRequestSendingRequest: state is null"); Debug.Assert(state.RequestHandle != null, "OnRequestSendingRequest: state.RequestHandle is null"); if (state.RequestMessage.RequestUri.Scheme != UriScheme.Https) { // Not SSL/TLS. return; } // Grab the channel binding token (CBT) information from the request handle and put it into // the TransportContext object. state.TransportContext.SetChannelBinding(state.RequestHandle); if (state.ServerCertificateValidationCallback != null) { IntPtr certHandle = IntPtr.Zero; uint certHandleSize = (uint)IntPtr.Size; if (!Interop.WinHttp.WinHttpQueryOption( state.RequestHandle, Interop.WinHttp.WINHTTP_OPTION_SERVER_CERT_CONTEXT, ref certHandle, ref certHandleSize)) { int lastError = Marshal.GetLastWin32Error(); throw WinHttpException.CreateExceptionUsingError(lastError); } // Create a managed wrapper around the certificate handle. Since this results in duplicating // the handle, we will close the original handle after creating the wrapper. var serverCertificate = new X509Certificate2(certHandle); Interop.Crypt32.CertFreeCertificateContext(certHandle); X509Chain chain = null; SslPolicyErrors sslPolicyErrors; try { WinHttpCertificateHelper.BuildChain( serverCertificate, state.RequestMessage.RequestUri.Host, state.CheckCertificateRevocationList, out chain, out sslPolicyErrors); bool result = state.ServerCertificateValidationCallback( state.RequestMessage, serverCertificate, chain, sslPolicyErrors); if (!result) { throw WinHttpException.CreateExceptionUsingError( (int)Interop.WinHttp.ERROR_WINHTTP_SECURE_FAILURE); } } finally { if (chain != null) { chain.Dispose(); } serverCertificate.Dispose(); } } }
public static HttpResponseMessage CreateResponseMessage( WinHttpRequestState state, bool doManualDecompressionCheck) { HttpRequestMessage request = state.RequestMessage; SafeWinHttpHandle requestHandle = state.RequestHandle; CookieUsePolicy cookieUsePolicy = state.Handler.CookieUsePolicy; CookieContainer cookieContainer = state.Handler.CookieContainer; var response = new HttpResponseMessage(); bool stripEncodingHeaders = false; // Create a single buffer to use for all subsequent WinHttpQueryHeaders string interop calls. // This buffer is the length needed for WINHTTP_QUERY_RAW_HEADERS_CRLF, which includes the status line // and all headers separated by CRLF, so it should be large enough for any individual status line or header queries. int bufferLength = GetResponseHeaderCharBufferLength(requestHandle, Interop.WinHttp.WINHTTP_QUERY_RAW_HEADERS_CRLF); char[] buffer = new char[bufferLength]; // Get HTTP version, status code, reason phrase from the response headers. int versionLength = GetResponseHeader(requestHandle, Interop.WinHttp.WINHTTP_QUERY_VERSION, buffer); response.Version = CharArrayHelpers.EqualsOrdinalAsciiIgnoreCase("HTTP/1.1", buffer, 0, versionLength) ? HttpVersion.Version11 : CharArrayHelpers.EqualsOrdinalAsciiIgnoreCase("HTTP/1.0", buffer, 0, versionLength) ? HttpVersion.Version10 : HttpVersion.Unknown; response.StatusCode = (HttpStatusCode)GetResponseHeaderNumberInfo( requestHandle, Interop.WinHttp.WINHTTP_QUERY_STATUS_CODE); int reasonPhraseLength = GetResponseHeader(requestHandle, Interop.WinHttp.WINHTTP_QUERY_STATUS_TEXT, buffer); response.ReasonPhrase = reasonPhraseLength > 0 ? GetReasonPhrase(response.StatusCode, buffer, reasonPhraseLength) : string.Empty; // Create response stream and wrap it in a StreamContent object. var responseStream = new WinHttpResponseStream(requestHandle, state); state.RequestHandle = null; // ownership successfully transfered to WinHttpResponseStram. Stream decompressedStream = responseStream; if (doManualDecompressionCheck) { int contentEncodingStartIndex = 0; int contentEncodingLength = GetResponseHeader( requestHandle, Interop.WinHttp.WINHTTP_QUERY_CONTENT_ENCODING, buffer); CharArrayHelpers.Trim(buffer, ref contentEncodingStartIndex, ref contentEncodingLength); if (contentEncodingLength > 0) { if (CharArrayHelpers.EqualsOrdinalAsciiIgnoreCase( EncodingNameGzip, buffer, contentEncodingStartIndex, contentEncodingLength)) { decompressedStream = new GZipStream(responseStream, CompressionMode.Decompress); stripEncodingHeaders = true; } else if (CharArrayHelpers.EqualsOrdinalAsciiIgnoreCase( EncodingNameDeflate, buffer, contentEncodingStartIndex, contentEncodingLength)) { decompressedStream = new DeflateStream(responseStream, CompressionMode.Decompress); stripEncodingHeaders = true; } } } var content = new StreamContent(decompressedStream); response.Content = content; response.RequestMessage = request; // Parse raw response headers and place them into response message. ParseResponseHeaders(requestHandle, response, buffer, stripEncodingHeaders); if (response.RequestMessage.Method != HttpMethod.Head) { state.ExpectedBytesToRead = response.Content.Headers.ContentLength; } return(response); }
internal WinHttpResponseStream(SafeWinHttpHandle requestHandle, WinHttpRequestState state) { _state = state; _requestHandle = requestHandle; }
public void CheckResponseForAuthentication( WinHttpRequestState state, ref uint proxyAuthScheme, ref uint serverAuthScheme) { uint supportedSchemes = 0; uint firstSchemeIgnored = 0; uint authTarget = 0; Uri uri = state.RequestMessage.RequestUri; state.RetryRequest = false; // Check the status code and retry the request applying credentials if needed. var statusCode = (HttpStatusCode)WinHttpResponseParser.GetResponseHeaderNumberInfo( state.RequestHandle, Interop.WinHttp.WINHTTP_QUERY_STATUS_CODE); switch (statusCode) { case HttpStatusCode.Unauthorized: if (state.ServerCredentials == null || state.LastStatusCode == HttpStatusCode.Unauthorized) { // Either we don't have server credentials or we already tried // to set the credentials and it failed before. // So we will let the 401 be the final status code returned. break; } state.LastStatusCode = statusCode; // Determine authorization scheme to use. We ignore the firstScheme // parameter which is included in the supportedSchemes flags already. // We pass the schemes to ChooseAuthScheme which will pick the scheme // based on most secure scheme to least secure scheme ordering. if (!Interop.WinHttp.WinHttpQueryAuthSchemes( state.RequestHandle, out supportedSchemes, out firstSchemeIgnored, out authTarget)) { // WinHTTP returns an error for schemes it doesn't handle. // So, we need to ignore the error and just let it stay at 401. break; } // WinHTTP returns the proper authTarget based on the status code (401, 407). // But we can validate with assert. Debug.Assert(authTarget == Interop.WinHttp.WINHTTP_AUTH_TARGET_SERVER); serverAuthScheme = ChooseAuthScheme(supportedSchemes); if (serverAuthScheme != 0) { if (SetWinHttpCredential( state.RequestHandle, state.ServerCredentials, uri, serverAuthScheme, authTarget)) { state.RetryRequest = true; } } break; case HttpStatusCode.ProxyAuthenticationRequired: if (state.LastStatusCode == HttpStatusCode.ProxyAuthenticationRequired) { // We tried already to set the credentials. break; } state.LastStatusCode = statusCode; // If we don't have any proxy credentials to try, then we end up with 407. ICredentials proxyCreds = state.Proxy == null ? state.DefaultProxyCredentials : state.Proxy.Credentials; if (proxyCreds == null) { break; } // Determine authorization scheme to use. We ignore the firstScheme // parameter which is included in the supportedSchemes flags already. // We pass the schemes to ChooseAuthScheme which will pick the scheme // based on most secure scheme to least secure scheme ordering. if (!Interop.WinHttp.WinHttpQueryAuthSchemes( state.RequestHandle, out supportedSchemes, out firstSchemeIgnored, out authTarget)) { // WinHTTP returns an error for schemes it doesn't handle. // So, we need to ignore the error and just let it stay at 401. break; } // WinHTTP returns the proper authTarget based on the status code (401, 407). // But we can validate with assert. Debug.Assert(authTarget == Interop.WinHttp.WINHTTP_AUTH_TARGET_PROXY); proxyAuthScheme = ChooseAuthScheme(supportedSchemes); state.RetryRequest = true; break; default: if (state.PreAuthenticate && serverAuthScheme != 0) { SaveServerCredentialsToCache(uri, serverAuthScheme, state.ServerCredentials); } break; } }