private static void EventSourceTrace <TArg0, TArg1, TArg2> (string formatMessage, TArg0 arg0, TArg1 arg1, TArg2 arg2, MultiAgent agent = null, EasyRequest easy = null, [CallerMemberName] string memberName = null) { if (EventSourceTracingEnabled) { EventSourceTraceCore(string.Format(formatMessage, arg0, arg1, arg2), agent, easy, memberName); } }
private static void EventSourceTrace( string message, MultiAgent agent = null, EasyRequest easy = null, [CallerMemberName] string memberName = null) { if (EventSourceTracingEnabled) { EventSourceTraceCore(message, agent, easy, memberName); } }
/// <summary>Queues a request for the multi handle to process.</summary> /// <param name="request"></param> public void Queue(EasyRequest request) { lock (_incomingRequests) { // Add the request, then initiate processing. _incomingRequests.Enqueue(request); EnsureWorkerIsRunning(); } }
private static void SetSslVersion(EasyRequest easy, IntPtr sslCtx = default(IntPtr)) { // Get the requested protocols. SslProtocols protocols = easy._handler.SslProtocols; if (protocols == SslProtocols.None) { // Let libcurl use its defaults if None is set. return; } // libcurl supports options for either enabling all of the TLS1.* protocols or enabling // just one protocol; it doesn't currently support enabling two of the three, e.g. you can't // pick TLS1.1 and TLS1.2 but not TLS1.0, but you can select just TLS1.2. Interop.Http.CurlSslVersion curlSslVersion; switch (protocols) { #pragma warning disable 0618 // SSL2/3 are deprecated case SslProtocols.Ssl2: curlSslVersion = Interop.Http.CurlSslVersion.CURL_SSLVERSION_SSLv2; break; case SslProtocols.Ssl3: curlSslVersion = Interop.Http.CurlSslVersion.CURL_SSLVERSION_SSLv3; break; #pragma warning restore 0618 case SslProtocols.Tls: curlSslVersion = Interop.Http.CurlSslVersion.CURL_SSLVERSION_TLSv1_0; break; case SslProtocols.Tls11: curlSslVersion = Interop.Http.CurlSslVersion.CURL_SSLVERSION_TLSv1_1; break; case SslProtocols.Tls12: curlSslVersion = Interop.Http.CurlSslVersion.CURL_SSLVERSION_TLSv1_2; break; case SslProtocols.Tls | SslProtocols.Tls11 | SslProtocols.Tls12: curlSslVersion = Interop.Http.CurlSslVersion.CURL_SSLVERSION_TLSv1; break; default: throw new NotSupportedException(SR.net_securityprotocolnotsupported); } try { easy.SetCurlOption(Interop.Http.CURLoption.CURLOPT_SSLVERSION, (long)curlSslVersion); } catch (CurlException e) when(e.HResult == (int)CURLcode.CURLE_UNKNOWN_OPTION) { throw new NotSupportedException(SR.net_securityprotocolnotsupported, e); } }
private void ActivateNewRequest(SafeCurlMultiHandle multiHandle, EasyRequest easy) { Debug.Assert(easy != null, "We should never get a null request"); Debug.Assert(easy._associatedMultiAgent == null, "New requests should not be associated with an agent yet"); // If cancellation has been requested, complete the request proactively if (easy._cancellationToken.IsCancellationRequested) { easy.FailRequest(new OperationCanceledException(easy._cancellationToken)); easy.Cleanup(); // no active processing remains, so cleanup return; } // Otherwise, configure it. Most of the configuration was already done when the EasyRequest // was created, but there's additional configuration we need to do specific to this // multi agent, specifically telling the easy request about its own GCHandle and setting // up callbacks for data processing. Once it's configured, add it to the multi handle. GCHandle gcHandle = GCHandle.Alloc(easy); IntPtr gcHandlePtr = GCHandle.ToIntPtr(gcHandle); try { easy._associatedMultiAgent = this; easy.SetCurlOption(CURLoption.CURLOPT_PRIVATE, gcHandlePtr); easy.SetCurlCallbacks(gcHandlePtr, s_receiveHeadersCallback, s_sendCallback, s_seekCallback, s_receiveBodyCallback); ThrowIfCURLMError(Interop.Http.MultiAddHandle(multiHandle, easy._easyHandle)); } catch (Exception exc) { gcHandle.Free(); easy.FailRequest(exc); easy.Cleanup(); // no active processing remains, so cleanup return; } // And if cancellation can be requested, hook up a cancellation callback. // This callback will put the easy request back into the queue, which will // ensure that a wake-up request has been issued. When we pull // the easy request out of the request queue, we'll see that it's already // associated with this agent, meaning that it's a cancellation request, // and we'll deal with it appropriately. var cancellationReg = default(CancellationTokenRegistration); if (easy._cancellationToken.CanBeCanceled) { cancellationReg = easy._cancellationToken.Register(s => { var state = (Tuple<MultiAgent, EasyRequest>)s; state.Item1.Queue(new IncomingRequest { Easy = state.Item2, Type = IncomingRequestType.Cancel }); }, Tuple.Create<MultiAgent, EasyRequest>(this, easy)); } // Finally, add it to our map. _activeOperations.Add( gcHandlePtr, new ActiveRequest { Easy = easy, CancellationRegistration = cancellationReg }); }
private static void EventSourceTraceCore(string message, MultiAgent agent, EasyRequest easy, string memberName) { // If we weren't handed a multi agent, see if we can get one from the EasyRequest if (agent == null && easy != null) { agent = easy._associatedMultiAgent; } NetEventSource.Log.HandlerMessage( (agent?.RunningWorkerId).GetValueOrDefault(), easy?.Task.Id ?? 0, memberName, message); }
private static void SetSslVersion(EasyRequest easy) { // Get the requested protocols. SslProtocols protocols = easy._handler.SslProtocols; if (protocols == SslProtocols.None) { // Let libcurl use its defaults if None is set. return; } // We explicitly disallow choosing SSL2/3. Make sure they were filtered out. Debug.Assert((protocols & ~SecurityProtocol.AllowedSecurityProtocols) == 0, "Disallowed protocols should have been filtered out."); // libcurl supports options for either enabling all of the TLS1.* protocols or enabling // just one of them; it doesn't currently support enabling two of the three, e.g. you can't // pick TLS1.1 and TLS1.2 but not TLS1.0, but you can select just TLS1.2. Interop.Http.CurlSslVersion curlSslVersion; switch (protocols) { case SslProtocols.Tls: curlSslVersion = Interop.Http.CurlSslVersion.CURL_SSLVERSION_TLSv1_0; break; case SslProtocols.Tls11: curlSslVersion = Interop.Http.CurlSslVersion.CURL_SSLVERSION_TLSv1_1; break; case SslProtocols.Tls12: curlSslVersion = Interop.Http.CurlSslVersion.CURL_SSLVERSION_TLSv1_2; break; case SslProtocols.Tls | SslProtocols.Tls11 | SslProtocols.Tls12: curlSslVersion = Interop.Http.CurlSslVersion.CURL_SSLVERSION_TLSv1; break; default: throw new NotSupportedException(SR.net_securityprotocolnotsupported); } try { easy.SetCurlOption(Interop.Http.CURLoption.CURLOPT_SSLVERSION, (long)curlSslVersion); } catch (CurlException e) when(e.HResult == (int)CURLcode.CURLE_UNKNOWN_OPTION) { throw new NotSupportedException(SR.net_securityprotocolnotsupported, e); } }
private void ConfigureAndQueue(EasyRequest easy) { try { easy.InitializeCurl(); _agent.Queue(new MultiAgent.IncomingRequest { Easy = easy, Type = MultiAgent.IncomingRequestType.New }); } catch (Exception exc) { easy.FailRequest(exc); easy.Cleanup(); // no active processing remains, so we can cleanup } }
private static bool TryGetEasyRequest(IntPtr curlPtr, out EasyRequest easy) { Debug.Assert(curlPtr != IntPtr.Zero, "curlPtr is not null"); IntPtr gcHandlePtr; CURLcode getInfoResult = Interop.Http.EasyGetInfoPointer(curlPtr, CURLINFO.CURLINFO_PRIVATE, out gcHandlePtr); if (getInfoResult == CURLcode.CURLE_OK) { return(MultiAgent.TryGetEasyRequestFromGCHandle(gcHandlePtr, out easy)); } Debug.Fail($"Failed to get info on a completing easy handle: {getInfoResult}"); easy = null; return(false); }
private void FindAndFailActiveRequest(SafeCurlMultiHandle multiHandle, EasyRequest easy, Exception error) { VerboseTrace("Error: " + error.Message, easy: easy); IntPtr gcHandlePtr; ActiveRequest activeRequest; if (FindActiveRequest(easy, out gcHandlePtr, out activeRequest)) { DeactivateActiveRequest(multiHandle, easy, gcHandlePtr, activeRequest.CancellationRegistration); easy.FailRequest(error); easy.Cleanup(); // no active processing remains, so we can cleanup } else { Debug.Assert(easy.Task.IsCompleted, "We should only not be able to find the request if it failed or we started to send back the response."); } }
private static bool TryGetEasyRequest(IntPtr curlPtr, out EasyRequest easy) { Debug.Assert(curlPtr != IntPtr.Zero, "curlPtr is not null"); IntPtr gcHandlePtr; CURLcode getInfoResult = Interop.Http.EasyGetInfoPointer(curlPtr, CURLINFO.CURLINFO_PRIVATE, out gcHandlePtr); Debug.Assert(getInfoResult == CURLcode.CURLE_OK, "Failed to get info on a completing easy handle"); if (getInfoResult == CURLcode.CURLE_OK) { GCHandle handle = GCHandle.FromIntPtr(gcHandlePtr); easy = handle.Target as EasyRequest; Debug.Assert(easy != null, "Expected non-null EasyRequest in GCHandle"); return easy != null; } easy = null; return false; }
internal static void SetSslOptions(EasyRequest easy, ClientCertificateOption clientCertOption) { // Disable SSLv2/SSLv3, allow TLSv1.* easy.SetCurlOption(Interop.Http.CURLoption.CURLOPT_SSLVERSION, (long)Interop.Http.CurlSslVersion.CURL_SSLVERSION_TLSv1); IntPtr userPointer = IntPtr.Zero; if (clientCertOption == ClientCertificateOption.Automatic) { ClientCertificateProvider certProvider = new ClientCertificateProvider(); userPointer = GCHandle.ToIntPtr(certProvider._gcHandle); easy.Task.ContinueWith((_, state) => ((IDisposable)state).Dispose(), certProvider, CancellationToken.None, TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default); } else { Debug.Assert(clientCertOption == ClientCertificateOption.Manual, "ClientCertificateOption is manual or automatic"); } CURLcode answer = easy.SetSslCtxCallback(s_sslCtxCallback, userPointer); switch (answer) { case CURLcode.CURLE_OK: break; // Curl 7.38 and prior case CURLcode.CURLE_UNKNOWN_OPTION: // Curl 7.39 and later case CURLcode.CURLE_NOT_BUILT_IN: EventSourceTrace("CURLOPT_SSL_CTX_FUNCTION not supported. Platform default HTTPS chain building in use"); if (clientCertOption == ClientCertificateOption.Automatic) { throw new PlatformNotSupportedException(SR.net_http_unix_invalid_client_cert_option); } break; default: ThrowIfCURLEError(answer); break; } }
private bool FindActiveRequest(EasyRequest easy, out IntPtr gcHandlePtr, out ActiveRequest activeRequest) { // We maintain an IntPtr=>ActiveRequest mapping, which makes it cheap to look-up by GCHandle ptr but // expensive to look up by EasyRequest. If we find this becoming a bottleneck, we can add a reverse // map that stores the other direction as well. foreach (KeyValuePair<IntPtr, ActiveRequest> pair in _activeOperations) { if (pair.Value.Easy == easy) { gcHandlePtr = pair.Key; activeRequest = pair.Value; return true; } } gcHandlePtr = IntPtr.Zero; activeRequest = default(ActiveRequest); return false; }
private static void AddChannelBindingToken(X509Certificate2 certificate, IntPtr curlPtr) { Debug.Assert(curlPtr != IntPtr.Zero, "curlPtr is not null"); IntPtr gcHandlePtr; CURLcode getInfoResult = Interop.Http.EasyGetInfoPointer(curlPtr, CURLINFO.CURLINFO_PRIVATE, out gcHandlePtr); Debug.Assert(getInfoResult == CURLcode.CURLE_OK, "Failed to get info on a completing easy handle"); if (getInfoResult == CURLcode.CURLE_OK) { GCHandle handle = GCHandle.FromIntPtr(gcHandlePtr); EasyRequest easy = handle.Target as EasyRequest; Debug.Assert(easy != null, "Expected non-null EasyRequest in GCHandle"); if (easy._requestContentStream != null) { easy._requestContentStream.SetChannelBindingToken(certificate); } } }
private void HandleIncomingRequest(SafeCurlMultiHandle multiHandle, IncomingRequest request) { Debug.Assert(!Monitor.IsEntered(_incomingRequests), "Incoming requests lock should only be held while accessing the queue"); VerboseTrace("Type: " + request.Type, easy: request.Easy); EasyRequest easy = request.Easy; switch (request.Type) { case IncomingRequestType.New: ActivateNewRequest(multiHandle, easy); break; case IncomingRequestType.Cancel: Debug.Assert(easy._associatedMultiAgent == this, "Should only cancel associated easy requests"); Debug.Assert(easy._cancellationToken.IsCancellationRequested, "Cancellation should have been requested"); FindAndFailActiveRequest(multiHandle, easy, new OperationCanceledException(easy._cancellationToken)); break; case IncomingRequestType.Unpause: Debug.Assert(easy._associatedMultiAgent == this, "Should only unpause associated easy requests"); if (!easy._easyHandle.IsClosed) { IntPtr gcHandlePtr; ActiveRequest ar; Debug.Assert(FindActiveRequest(easy, out gcHandlePtr, out ar), "Couldn't find active request for unpause"); CURLcode unpauseResult = Interop.Http.EasyUnpause(easy._easyHandle); try { ThrowIfCURLEError(unpauseResult); } catch (Exception exc) { FindAndFailActiveRequest(multiHandle, easy, exc); } } break; default: Debug.Fail("Invalid request type: " + request.Type); break; } }
private static void HandleRedirectLocationHeader(EasyRequest state, string locationValue) { Debug.Assert(state._isRedirect); Debug.Assert(state._handler.AutomaticRedirection); string location = locationValue.Trim(); //only for absolute redirects Uri forwardUri; if (Uri.TryCreate(location, UriKind.RelativeOrAbsolute, out forwardUri) && forwardUri.IsAbsoluteUri) { // Just as with WinHttpHandler, for security reasons, we drop the server credential if it is // anything other than a CredentialCache. We allow credentials in a CredentialCache since they // are specifically tied to URIs. var creds = state._handler.Credentials as CredentialCache; KeyValuePair <NetworkCredential, CURLAUTH> ncAndScheme = GetCredentials(forwardUri, creds, AuthTypesPermittedByCredentialKind(creds)); if (ncAndScheme.Key != null) { state.SetCredentialsOptions(ncAndScheme); } else { state.SetCurlOption(CURLoption.CURLOPT_USERNAME, IntPtr.Zero); state.SetCurlOption(CURLoption.CURLOPT_PASSWORD, IntPtr.Zero); } // reset proxy - it is possible that the proxy has different credentials for the new URI state.SetProxyOptions(forwardUri); if (state._handler._useCookie) { // reset cookies. state.SetCurlOption(CURLoption.CURLOPT_COOKIE, IntPtr.Zero); // set cookies again state.SetCookieOption(forwardUri); } state.SetRedirectUri(forwardUri); } // set the headers again. This is a workaround for libcurl's limitation in handling headers with empty values state.SetRequestHeaders(); }
internal static void SetSslOptions(EasyRequest easy) { CURLcode answer = easy.SetSslCtxCallback(s_sslCtxCallback); switch (answer) { case CURLcode.CURLE_OK: break; // Curl 7.38 and prior case CURLcode.CURLE_UNKNOWN_OPTION: // Curl 7.39 and later case CURLcode.CURLE_NOT_BUILT_IN: VerboseTrace("CURLOPT_SSL_CTX_FUNCTION is not supported, platform default https chain building in use"); break; default: ThrowIfCURLEError(answer); break; } }
internal static void SetSslOptions(EasyRequest easy) { int answer = Interop.libcurl.curl_easy_setopt( easy._easyHandle, Interop.libcurl.CURLoption.CURLOPT_SSL_CTX_FUNCTION, s_sslCtxCallback); switch (answer) { case Interop.libcurl.CURLcode.CURLE_OK: break; case Interop.libcurl.CURLcode.CURLE_NOT_BUILT_IN: VerboseTrace("CURLOPT_SSL_CTX_FUNCTION is not supported, platform default https chain building in use"); break; default: ThrowIfCURLEError(answer); break; } }
private void AddResponseCookies(EasyRequest state, string cookieHeader) { if (!_useCookie) { return; } try { _cookieContainer.SetCookies(state._targetUri, cookieHeader); state.SetCookieOption(state._requestMessage.RequestUri); } catch (CookieException e) { EventSourceTrace( "Malformed cookie parsing failed: {0}, server: {1}, cookie: {2}", e.Message, state._requestMessage.RequestUri, cookieHeader); } }
internal static void SetSslOptions(EasyRequest easy, ClientCertificateOption clientCertOption) { EventSourceTrace("ClientCertificateOption: {0}", clientCertOption, easy: easy); Debug.Assert(clientCertOption == ClientCertificateOption.Automatic || clientCertOption == ClientCertificateOption.Manual); // Create a client certificate provider if client certs may be used. X509Certificate2Collection clientCertificates = easy._handler._clientCertificates; ClientCertificateProvider certProvider = clientCertOption == ClientCertificateOption.Automatic ? new ClientCertificateProvider(null) : // automatic clientCertificates?.Count > 0 ? new ClientCertificateProvider(clientCertificates) : // manual with certs null; // manual without certs IntPtr userPointer = IntPtr.Zero; if (certProvider != null) { EventSourceTrace("Created certificate provider", easy: easy); // The client cert provider needs to be passed through to the callback, and thus // we create a GCHandle to keep it rooted. This handle needs to be cleaned up // when the request has completed, and a simple and pay-for-play way to do that // is by cleaning it up in a continuation off of the request. userPointer = GCHandle.ToIntPtr(certProvider._gcHandle); easy.Task.ContinueWith((_, state) => ((IDisposable)state).Dispose(), certProvider, CancellationToken.None, TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default); } // Configure the options. Our best support is when targeting OpenSSL/1.0. For other backends, // we fall back to a minimal amount of support, and may throw a PNSE based on the options requested. if (Interop.Http.HasMatchingOpenSslVersion) { // Register the callback with libcurl. We need to register even if there's no user-provided // server callback and even if there are no client certificates, because we support verifying // server certificates against more than those known to OpenSSL. SetSslOptionsForSupportedBackend(easy, certProvider, userPointer); } else { // Newer versions of OpenSSL, and other non-OpenSSL backends, do not currently support callbacks. // That means we'll throw a PNSE if a callback is required. SetSslOptionsForUnsupportedBackend(easy, certProvider); } }
internal CurlResponseMessage(EasyRequest easy) { Debug.Assert(easy != null, "Expected non-null EasyRequest"); RequestMessage = easy._requestMessage; ResponseStream = new CurlResponseStream(easy); Content = new StreamContent(ResponseStream); // On Windows, we pass the equivalent of the easy._cancellationToken // in to StreamContent's ctor. This in turn passes that token through // to ReadAsync operations on the stream response stream when HttpClient // reads from the response stream to buffer it with ResponseContentRead. // We don't need to do that here in the Unix implementation as, until the // SendAsync task completes, the handler will have registered with the // CancellationToken with an action that will cancel all work related to // the easy handle if cancellation occurs, and that includes canceling any // pending reads on the response stream. It wouldn't hurt anything functionally // to still pass easy._cancellationToken here, but it will increase costs // a bit, as each read will then need to allocate a larger state object as // well as register with and unregister from the cancellation token. }
private static void SetSslOptionsForCertificateStore(EasyRequest easy) { // Support specifying certificate directory/bundle via environment variables: SSL_CERT_DIR, SSL_CERT_FILE. GetSslCaLocations(out string sslCaPath, out string sslCaInfo); if (sslCaPath != string.Empty) { easy.SetCurlOption(Interop.Http.CURLoption.CURLOPT_CAPATH, sslCaPath); // https proxy support requires libcurl 7.52.0+ easy.TrySetCurlOption(Interop.Http.CURLoption.CURLOPT_PROXY_CAPATH, sslCaPath); } if (sslCaInfo != string.Empty) { easy.SetCurlOption(Interop.Http.CURLoption.CURLOPT_CAINFO, sslCaInfo); // https proxy support requires libcurl 7.52.0+ easy.TrySetCurlOption(Interop.Http.CURLoption.CURLOPT_PROXY_CAINFO, sslCaInfo); } }
private void AddResponseCookies(EasyRequest state, string cookieHeader) { if (!_useCookie) { return; } try { _cookieContainer.SetCookies(state._targetUri, cookieHeader); state.SetCookieOption(state._requestMessage.RequestUri); } catch (CookieException e) { string msg = string.Format("Malformed cookie: SetCookies Failed with {0}, server: {1}, cookie:{2}", e.Message, state._requestMessage.RequestUri, cookieHeader); VerboseTrace(msg); } }
private void FinishRequest(EasyRequest completedOperation, CURLcode messageResult) { VerboseTrace("messageResult: " + messageResult, easy: completedOperation); if (completedOperation._responseMessage.StatusCode != HttpStatusCode.Unauthorized) { if (completedOperation._handler.PreAuthenticate) { long authAvailable; if (Interop.Http.EasyGetInfoLong(completedOperation._easyHandle, CURLINFO.CURLINFO_HTTPAUTH_AVAIL, out authAvailable) == CURLcode.CURLE_OK) { completedOperation._handler.AddCredentialToCache( completedOperation._requestMessage.RequestUri, (CURLAUTH)authAvailable, completedOperation._networkCredential); } // Ignore errors: no need to fail for the sake of putting the credentials into the cache } completedOperation._handler.AddResponseCookies( completedOperation._requestMessage.RequestUri, completedOperation._responseMessage); } // Complete or fail the request try { bool unsupportedProtocolRedirect = messageResult == CURLcode.CURLE_UNSUPPORTED_PROTOCOL && completedOperation._isRedirect; if (!unsupportedProtocolRedirect) { ThrowIfCURLEError(messageResult); } completedOperation.EnsureResponseMessagePublished(); } catch (Exception exc) { completedOperation.FailRequest(exc); } // At this point, we've completed processing the entire request, either due to error // or due to completing the entire response. completedOperation.Cleanup(); }
private static void HandleRedirectLocationHeader(EasyRequest state, string locationValue) { Debug.Assert(state._isRedirect); Debug.Assert(state._handler.AutomaticRedirection); string location = locationValue.Trim(); //only for absolute redirects Uri forwardUri; if (Uri.TryCreate(location, UriKind.RelativeOrAbsolute, out forwardUri) && forwardUri.IsAbsoluteUri) { KeyValuePair <NetworkCredential, CURLAUTH> ncAndScheme = GetCredentials(state._handler.Credentials as CredentialCache, forwardUri); if (ncAndScheme.Key != null) { state.SetCredentialsOptions(ncAndScheme); } else { state.SetCurlOption(CURLoption.CURLOPT_USERNAME, IntPtr.Zero); state.SetCurlOption(CURLoption.CURLOPT_PASSWORD, IntPtr.Zero); } // reset proxy - it is possible that the proxy has different credentials for the new URI state.SetProxyOptions(forwardUri); if (state._handler._useCookie) { // reset cookies. state.SetCurlOption(CURLoption.CURLOPT_COOKIE, IntPtr.Zero); // set cookies again state.SetCookieOption(forwardUri); } state.SetRedirectUri(forwardUri); } // set the headers again. This is a workaround for libcurl's limitation in handling headers with empty values state.SetRequestHeaders(); }
private static bool TryGetEasyRequestFromContext(IntPtr context, out EasyRequest easy) { // Get the EasyRequest from the context try { GCHandle handle = GCHandle.FromIntPtr(context); easy = (EasyRequest)handle.Target; Debug.Assert(easy != null, "Expected non-null EasyRequest in GCHandle"); return easy != null; } catch (InvalidCastException) { Debug.Fail("EasyRequest wasn't the GCHandle's Target"); } catch (InvalidOperationException) { Debug.Fail("Invalid GCHandle"); } easy = null; return false; }
private static void SetSslOptionsForCertificateStore(EasyRequest easy) { // Support specifying certificate directory/bundle via environment variables: SSL_CERT_DIR, SSL_CERT_FILE. string sslCertDir = Environment.GetEnvironmentVariable("SSL_CERT_DIR"); if (sslCertDir != null) { easy.SetCurlOption(Interop.Http.CURLoption.CURLOPT_CAPATH, sslCertDir); // https proxy support requires libcurl 7.52.0+ easy.TrySetCurlOption(Interop.Http.CURLoption.CURLOPT_PROXY_CAPATH, sslCertDir); } string sslCertFile = Environment.GetEnvironmentVariable("SSL_CERT_FILE"); if (sslCertFile != null) { easy.SetCurlOption(Interop.Http.CURLoption.CURLOPT_CAINFO, sslCertFile); // https proxy support requires libcurl 7.52.0+ easy.TrySetCurlOption(Interop.Http.CURLoption.CURLOPT_PROXY_CAINFO, sslCertFile); } }
private static void SetCurlCallbacks(EasyRequest easy, IntPtr easyGCHandle) { // Add callback for processing headers easy.SetCurlOption(CURLoption.CURLOPT_HEADERFUNCTION, s_receiveHeadersCallback); easy.SetCurlOption(CURLoption.CURLOPT_HEADERDATA, easyGCHandle); // If we're sending data as part of the request, add callbacks for sending request data if (easy.RequestMessage.Content != null) { easy.SetCurlOption(CURLoption.CURLOPT_READFUNCTION, s_sendCallback); easy.SetCurlOption(CURLoption.CURLOPT_READDATA, easyGCHandle); easy.SetCurlOption(CURLoption.CURLOPT_SEEKFUNCTION, s_seekCallback); easy.SetCurlOption(CURLoption.CURLOPT_SEEKDATA, easyGCHandle); } // If we're expecting any data in response, add a callback for receiving body data if (easy.RequestMessage.Method != HttpMethod.Head) { easy.SetCurlOption(CURLoption.CURLOPT_WRITEFUNCTION, s_receiveBodyCallback); easy.SetCurlOption(CURLoption.CURLOPT_WRITEDATA, easyGCHandle); } }
internal static void SetSslOptions(EasyRequest easy) { CURLcode answer = Interop.Http.EasySetOptionPointer( easy._easyHandle, Interop.Http.CURLoption.CURLOPT_SSL_CTX_FUNCTION, s_sslCtxCallback); switch (answer) { case CURLcode.CURLE_OK: break; // Curl 7.38 and prior case CURLcode.CURLE_UNKNOWN_OPTION: // Curl 7.39 and later case CURLcode.CURLE_NOT_BUILT_IN: VerboseTrace("CURLOPT_SSL_CTX_FUNCTION is not supported, platform default https chain building in use"); break; default: ThrowIfCURLEError(answer); break; } }
internal static void SetSslOptions(EasyRequest easy, ClientCertificateOption clientCertOption) { Debug.Assert(clientCertOption == ClientCertificateOption.Automatic || clientCertOption == ClientCertificateOption.Manual); // Create a client certificate provider if client certs may be used. X509Certificate2Collection clientCertificates = easy._handler._clientCertificates; ClientCertificateProvider certProvider = clientCertOption == ClientCertificateOption.Automatic ? new ClientCertificateProvider(null) : // automatic clientCertificates?.Count > 0 ? new ClientCertificateProvider(clientCertificates) : // manual with certs null; // manual without certs IntPtr userPointer = IntPtr.Zero; if (certProvider != null) { // The client cert provider needs to be passed through to the callback, and thus // we create a GCHandle to keep it rooted. This handle needs to be cleaned up // when the request has completed, and a simple and pay-for-play way to do that // is by cleaning it up in a continuation off of the request. userPointer = GCHandle.ToIntPtr(certProvider._gcHandle); easy.Task.ContinueWith((_, state) => ((IDisposable)state).Dispose(), certProvider, CancellationToken.None, TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default); } // Register the callback with libcurl. We need to register even if there's no user-provided // server callback and even if there are no client certificates, because we support verifying // server certificates against more than those known to OpenSSL. CURLcode answer = easy.SetSslCtxCallback(s_sslCtxCallback, userPointer); switch (answer) { case CURLcode.CURLE_OK: // We successfully registered. If we'll be invoking a user-provided callback to verify the server // certificate as part of that, disable libcurl's verification of the host name. The user's callback // needs to be given the opportunity to examine the cert, and our logic will determine whether // the host name matches and will inform the callback of that. if (easy._handler.ServerCertificateValidationCallback != null) { easy.SetCurlOption(Interop.Http.CURLoption.CURLOPT_SSL_VERIFYHOST, 0); // don't verify the peer cert's hostname // We don't change the SSL_VERIFYPEER setting, as setting it to 0 will cause // SSL and libcurl to ignore the result of the server callback. } // The allowed SSL protocols will be set in the configuration callback. break; case CURLcode.CURLE_UNKNOWN_OPTION: // Curl 7.38 and prior case CURLcode.CURLE_NOT_BUILT_IN: // Curl 7.39 and later // It's ok if we failed to register the callback if all of the defaults are in play // with relation to handling of certificates. But if that's not the case, failing to // register the callback will result in those options not being factored in, which is // a significant enough error that we need to fail. EventSourceTrace("CURLOPT_SSL_CTX_FUNCTION not supported: {0}", answer, easy: easy); if (certProvider != null || easy._handler.ServerCertificateValidationCallback != null || easy._handler.CheckCertificateRevocationList) { throw new PlatformNotSupportedException( SR.Format(SR.net_http_unix_invalid_certcallback_option, CurlVersionDescription, CurlSslVersionDescription)); } // Since there won't be a callback to configure the allowed SSL protocols, configure them here. SetSslVersion(easy); break; default: ThrowIfCURLEError(answer); break; } }
private static bool TryGetEasyRequest(IntPtr curlPtr, out EasyRequest easy) { Debug.Assert(curlPtr != IntPtr.Zero, "curlPtr is not null"); IntPtr gcHandlePtr; CURLcode getInfoResult = Interop.Http.EasyGetInfoPointer(curlPtr, CURLINFO.CURLINFO_PRIVATE, out gcHandlePtr); if (getInfoResult == CURLcode.CURLE_OK) { return MultiAgent.TryGetEasyRequestFromGCHandle(gcHandlePtr, out easy); } Debug.Fail($"Failed to get info on a completing easy handle: {getInfoResult}"); easy = null; return false; }
internal CurlResponseStream(EasyRequest easy) { Debug.Assert(easy != null, "Expected non-null associated EasyRequest"); _easy = easy; }
private static bool TryParseStatusLine(HttpResponseMessage response, string responseHeader, EasyRequest state) { if (!responseHeader.StartsWith(CurlResponseParseUtils.HttpPrefix, StringComparison.OrdinalIgnoreCase)) { return(false); } // Clear the header if status line is recieved again. This signifies that there are multiple response headers (like in redirection). response.Headers.Clear(); response.Content.Headers.Clear(); CurlResponseParseUtils.ReadStatusLine(response, responseHeader); state._isRedirect = state._handler.AutomaticRedirection && (response.StatusCode == HttpStatusCode.Redirect || response.StatusCode == HttpStatusCode.RedirectKeepVerb || response.StatusCode == HttpStatusCode.RedirectMethod); return(true); }
/// <summary>Requests that libcurl unpause the connection associated with this request.</summary> internal void RequestUnpause(EasyRequest easy) { VerboseTrace(easy: easy); Queue(new IncomingRequest { Easy = easy, Type = IncomingRequestType.Unpause }); }
internal static void SetSslOptions(EasyRequest easy, ClientCertificateOption clientCertOption) { Debug.Assert( clientCertOption == ClientCertificateOption.Automatic || clientCertOption == ClientCertificateOption.Manual); // Create a client certificate provider if client certs may be used. X509Certificate2Collection clientCertificates = easy._handler._clientCertificates; if (clientCertOption != ClientCertificateOption.Manual || clientCertificates?.Count > 0) { // libcurl does not have an option of accepting a SecIdentityRef via an input option, // only via writing it to a file and letting it load the PFX. // This would require that a) we write said file, and b) that it contaminate the default // keychain (because their PFX loader loads to the default keychain). throw new PlatformNotSupportedException(SR.net_http_libcurl_clientcerts_notsupported_os); } // Revocation checking is always on for darwinssl (SecureTransport). // If any other backend is used and revocation is requested, we can't guarantee // that assertion. if (easy._handler.CheckCertificateRevocationList && !CurlSslVersionDescription.Equals(Interop.Http.SecureTransportDescription)) { throw new PlatformNotSupportedException( SR.Format( SR.net_http_libcurl_revocation_notsupported_sslbackend, CurlVersionDescription, CurlSslVersionDescription, Interop.Http.SecureTransportDescription)); } if (easy._handler.ServerCertificateCustomValidationCallback != null) { // libcurl (as of 7.49.1) does not have any callback which can be registered which fires // between the time that a TLS/SSL handshake has offered up the server certificate and the // time that the HTTP request headers are written. Were there any callback, the option // CURLINFO_TLS_SSL_PTR could be queried (and the backend identifier validated to be // CURLSSLBACKEND_DARWINSSL). Then the SecTrustRef could be extracted to build the chain, // a la SslStream. // // Without the callback the matrix looks like: // * If default-trusted and callback-would-trust: No difference (except side effects, like logging). // * If default-trusted and callback-would-block: Data would have been sent in violation of user trust. // * If not-default-trusted and callback-would-not-trust: No difference (except side effects). // * If not-default-trusted and callback-would-trust: No data sent, which doesn't match user desires. // // Of the two "different" cases, sending when we shouldn't is worse, so that's the direction we // have to cater to. So we'll use default trust, and throw on any custom callback. // // The situation where system trust fails can be remedied by including the certificate into the // user's keychain and setting the SSL policy trust for it to "Always Trust". // Similarly, the "block this" could be attained by setting the SSL policy for a cert in the // keychain to "Never Trust". // // However, one case we can support is when we know all certificates will pass validation. // We can detect a key case of that: whether DangerousAcceptAnyServerCertificateValidator was used. if (easy.ServerCertificateValidationCallbackAcceptsAll) { EventSourceTrace("Warning: Disabling peer verification per {0}", nameof(HttpClientHandler.DangerousAcceptAnyServerCertificateValidator), easy: easy); easy.SetCurlOption(Interop.Http.CURLoption.CURLOPT_SSL_VERIFYPEER, 0); // don't verify the peer // Don't set CURLOPT_SSL_VERIFHOST to 0; doing so disables SNI with SecureTransport backend. if (!CurlSslVersionDescription.Equals(Interop.Http.SecureTransportDescription)) { easy.SetCurlOption(Interop.Http.CURLoption.CURLOPT_SSL_VERIFYHOST, 0); // don't verify the hostname } } else { throw new PlatformNotSupportedException(SR.net_http_libcurl_callback_notsupported_os); } } SetSslVersion(easy); }
private void ActivateNewRequest(SafeCurlMultiHandle multiHandle, EasyRequest easy) { Debug.Assert(easy != null, "We should never get a null request"); Debug.Assert(easy._associatedMultiAgent == null, "New requests should not be associated with an agent yet"); // If cancellation has been requested, complete the request proactively if (easy._cancellationToken.IsCancellationRequested) { easy.FailRequest(new OperationCanceledException(easy._cancellationToken)); easy.Cleanup(); // no active processing remains, so cleanup return; } // Otherwise, configure it. Most of the configuration was already done when the EasyRequest // was created, but there's additional configuration we need to do specific to this // multi agent, specifically telling the easy request about its own GCHandle and setting // up callbacks for data processing. Once it's configured, add it to the multi handle. GCHandle gcHandle = GCHandle.Alloc(easy); IntPtr gcHandlePtr = GCHandle.ToIntPtr(gcHandle); try { easy._associatedMultiAgent = this; easy.SetCurlOption(CURLoption.CURLOPT_PRIVATE, gcHandlePtr); SetCurlCallbacks(easy, gcHandlePtr); ThrowIfCURLMError(Interop.libcurl.curl_multi_add_handle(multiHandle, easy._easyHandle)); } catch (Exception exc) { gcHandle.Free(); easy.FailRequest(exc); easy.Cleanup(); // no active processing remains, so cleanup return; } // And if cancellation can be requested, hook up a cancellation callback. // This callback will put the easy request back into the queue, which will // ensure that a wake-up request has been issued. When we pull // the easy request out of the request queue, we'll see that it's already // associated with this agent, meaning that it's a cancellation request, // and we'll deal with it appropriately. var cancellationReg = default(CancellationTokenRegistration); if (easy._cancellationToken.CanBeCanceled) { cancellationReg = easy._cancellationToken.Register(s => { var state = (Tuple<MultiAgent, EasyRequest>)s; state.Item1.Queue(new IncomingRequest { Easy = state.Item2, Type = IncomingRequestType.Cancel }); }, Tuple.Create<MultiAgent, EasyRequest>(this, easy)); } // Finally, add it to our map. _activeOperations.Add( gcHandlePtr, new ActiveRequest { Easy = easy, CancellationRegistration = cancellationReg }); }
/// <summary> /// Transfers up to <paramref name="length"/> data from the <paramref name="easy"/>'s /// request content (non-memory) stream to the buffer. /// </summary> /// <returns>The number of bytes transferred.</returns> private static size_t TransferDataFromRequestStream(IntPtr buffer, int length, EasyRequest easy) { MultiAgent multi = easy._associatedMultiAgent; // First check to see whether there's any data available from a previous asynchronous read request. // If there is, the transfer state's Task field will be non-null, with its Result representing // the number of bytes read. The Buffer will then contain all of that read data. If the Count // is 0, then this is the first time we're checking that Task, and so we populate the Count // from that read result. After that, we can transfer as much data remains between Offset and // Count. Multiple callbacks may pull from that one read. EasyRequest.SendTransferState sts = easy._sendTransferState; if (sts != null) { // Is there a previous read that may still have data to be consumed? if (sts._task != null) { Debug.Assert(sts._task.IsCompleted, "The task must have completed if we're getting called back."); // Determine how many bytes were read on the last asynchronous read. // If nothing was read, then we're done and can simply return 0 to indicate // the end of the stream. int bytesRead = sts._task.GetAwaiter().GetResult(); // will throw if read failed Debug.Assert(bytesRead >= 0 && bytesRead <= sts._buffer.Length, "ReadAsync returned an invalid result length: " + bytesRead); if (bytesRead == 0) { multi.VerboseTrace("End of stream from stored task", easy: easy); sts.SetTaskOffsetCount(null, 0, 0); return 0; } // If Count is still 0, then this is the first time after the task completed // that we're examining the data: transfer the bytesRead to the Count. if (sts._count == 0) { multi.VerboseTrace("ReadAsync completed with bytes: " + bytesRead, easy: easy); sts._count = bytesRead; } // Now Offset and Count are both accurate. Determine how much data we can copy to libcurl... int availableData = sts._count - sts._offset; Debug.Assert(availableData > 0, "There must be some data still available."); // ... and copy as much of that as libcurl will allow. int bytesToCopy = Math.Min(availableData, length); Marshal.Copy(sts._buffer, sts._offset, buffer, bytesToCopy); multi.VerboseTrace("Copied " + bytesToCopy + " bytes from request stream", easy: easy); // Update the offset. If we've gone through all of the data, reset the state // so that the next time we're called back we'll do a new read. sts._offset += bytesToCopy; Debug.Assert(sts._offset <= sts._count, "Offset should never exceed count"); if (sts._offset == sts._count) { sts.SetTaskOffsetCount(null, 0, 0); } // Return the amount of data copied Debug.Assert(bytesToCopy > 0, "We should never return 0 bytes here."); return (size_t)bytesToCopy; } // sts was non-null but sts.Task was null, meaning there was no previous task/data // from which to satisfy any of this request. } else // sts == null { // Allocate a transfer state object to use for the remainder of this request. easy._sendTransferState = sts = new EasyRequest.SendTransferState(); } Debug.Assert(sts != null, "By this point we should have a transfer object"); Debug.Assert(sts._task == null, "There shouldn't be a task now."); Debug.Assert(sts._count == 0, "Count should be zero."); Debug.Assert(sts._offset == 0, "Offset should be zero."); // If we get here, there was no previously read data available to copy. // Initiate a new asynchronous read. Task<int> asyncRead = easy._requestContentStream.ReadAsyncInternal( sts._buffer, 0, Math.Min(sts._buffer.Length, length), easy._cancellationToken); Debug.Assert(asyncRead != null, "Badly implemented stream returned a null task from ReadAsync"); // Even though it's "Async", it's possible this read could complete synchronously or extremely quickly. // Check to see if it did, in which case we can also satisfy the libcurl request synchronously in this callback. if (asyncRead.IsCompleted) { multi.VerboseTrace("ReadAsync completed immediately", easy: easy); // Get the amount of data read. int bytesRead = asyncRead.GetAwaiter().GetResult(); // will throw if read failed if (bytesRead == 0) { multi.VerboseTrace("End of stream from quick returning ReadAsync", easy: easy); return 0; } // Copy as much as we can. int bytesToCopy = Math.Min(bytesRead, length); Debug.Assert(bytesToCopy > 0 && bytesToCopy <= sts._buffer.Length, "ReadAsync quickly returned an invalid result length: " + bytesToCopy); Marshal.Copy(sts._buffer, 0, buffer, bytesToCopy); multi.VerboseTrace("Copied " + bytesToCopy + " from quick returning ReadAsync", easy: easy); // If we read more than we were able to copy, stash it away for the next read. if (bytesToCopy < bytesRead) { multi.VerboseTrace("Stashing away " + (bytesRead - bytesToCopy) + " bytes for next read.", easy: easy); sts.SetTaskOffsetCount(asyncRead, bytesToCopy, bytesRead); } // Return the number of bytes read. return (size_t)bytesToCopy; } // Otherwise, the read completed asynchronously. Store the task, and hook up a continuation // such that the connection will be unpaused once the task completes. sts.SetTaskOffsetCount(asyncRead, 0, 0); asyncRead.ContinueWith((t, s) => { EasyRequest easyRef = (EasyRequest)s; easyRef._associatedMultiAgent.RequestUnpause(easyRef); }, easy, CancellationToken.None, TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default); // Then pause the connection. multi.VerboseTrace("Pausing the connection", easy: easy); return Interop.libcurl.CURL_READFUNC_PAUSE; }
/// <summary> /// Loads the request's request content stream asynchronously and /// then submits the request to the multi agent. /// </summary> private async Task<HttpResponseMessage> QueueOperationWithRequestContentAsync(EasyRequest easy) { Debug.Assert(easy._requestMessage.Content != null, "Expected request to have non-null request content"); easy._requestContentStream = await easy._requestMessage.Content.ReadAsStreamAsync().ConfigureAwait(false); if (easy._cancellationToken.IsCancellationRequested) { easy.FailRequest(new OperationCanceledException(easy._cancellationToken)); easy.Cleanup(); // no active processing remains, so we can cleanup } else { ConfigureAndQueue(easy); } return await easy.Task.ConfigureAwait(false); }
private static bool TryGetEasyRequest(IntPtr curlPtr, out EasyRequest easy) { Debug.Assert(curlPtr != IntPtr.Zero, "curlPtr is not null"); IntPtr gcHandlePtr; CURLcode getInfoResult = Interop.Http.EasyGetInfoPointer(curlPtr, CURLINFO.CURLINFO_PRIVATE, out gcHandlePtr); Debug.Assert(getInfoResult == CURLcode.CURLE_OK, "Failed to get info on a completing easy handle"); if (getInfoResult == CURLcode.CURLE_OK) { try { GCHandle handle = GCHandle.FromIntPtr(gcHandlePtr); easy = (EasyRequest)handle.Target; Debug.Assert(easy != null, "Expected non-null EasyRequest in GCHandle"); return easy != null; } catch (Exception e) { EventSourceTrace("Error getting state from GCHandle: {0}", e); Debug.Fail($"Exception in {nameof(TryGetEasyRequest)}", e.ToString()); } } easy = null; return false; }
private static void SetSslVersion(EasyRequest easy, IntPtr sslCtx = default(IntPtr)) { // Get the requested protocols. System.Security.Authentication.SslProtocols protocols = easy._handler.ActualSslProtocols; // We explicitly disallow choosing SSL2/3. Make sure they were filtered out. Debug.Assert((protocols & ~SecurityProtocol.AllowedSecurityProtocols) == 0, "Disallowed protocols should have been filtered out."); // libcurl supports options for either enabling all of the TLS1.* protocols or enabling // just one of them; it doesn't currently support enabling two of the three, e.g. you can't // pick TLS1.1 and TLS1.2 but not TLS1.0, but you can select just TLS1.2. Interop.Http.CurlSslVersion curlSslVersion; switch (protocols) { case System.Security.Authentication.SslProtocols.Tls: curlSslVersion = Interop.Http.CurlSslVersion.CURL_SSLVERSION_TLSv1_0; break; case System.Security.Authentication.SslProtocols.Tls11: curlSslVersion = Interop.Http.CurlSslVersion.CURL_SSLVERSION_TLSv1_1; break; case System.Security.Authentication.SslProtocols.Tls12: curlSslVersion = Interop.Http.CurlSslVersion.CURL_SSLVERSION_TLSv1_2; break; case System.Security.Authentication.SslProtocols.Tls | System.Security.Authentication.SslProtocols.Tls11 | System.Security.Authentication.SslProtocols.Tls12: curlSslVersion = Interop.Http.CurlSslVersion.CURL_SSLVERSION_TLSv1; break; default: throw new NotSupportedException(SR.net_securityprotocolnotsupported); } try { easy.SetCurlOption(Interop.Http.CURLoption.CURLOPT_SSLVERSION, (long)curlSslVersion); } catch (CurlException e) when (e.HResult == (int)CURLcode.CURLE_UNKNOWN_OPTION) { throw new NotSupportedException(SR.net_securityprotocolnotsupported, e); } }
protected internal override Task<HttpResponseMessage> SendAsync( HttpRequestMessage request, CancellationToken cancellationToken) { if (request == null) { throw new ArgumentNullException("request", SR.net_http_handler_norequest); } if ((request.RequestUri.Scheme != UriSchemeHttp) && (request.RequestUri.Scheme != UriSchemeHttps)) { throw NotImplemented.ByDesignWithMessage(SR.net_http_client_http_baseaddress_required); } if (request.RequestUri.Scheme == UriSchemeHttps && !s_supportsSSL) { throw new PlatformNotSupportedException(SR.net_http_unix_https_support_unavailable_libcurl); } if (request.Headers.TransferEncodingChunked.GetValueOrDefault() && (request.Content == null)) { throw new InvalidOperationException(SR.net_http_chunked_not_allowed_with_empty_content); } if (_useCookie && _cookieContainer == null) { throw new InvalidOperationException(SR.net_http_invalid_cookiecontainer); } // TODO: Check that SendAsync is not being called again for same request object. // Probably fix is needed in WinHttpHandler as well CheckDisposed(); SetOperationStarted(); // Do an initial cancellation check to avoid initiating the async operation if // cancellation has already been requested. After this, we'll rely on CancellationToken.Register // to notify us of cancellation requests and shut down the operation if possible. if (cancellationToken.IsCancellationRequested) { return Task.FromCanceled<HttpResponseMessage>(cancellationToken); } // Create the easy request. This associates the easy request with this handler and configures // it based on the settings configured for the handler. var easy = new EasyRequest(this, request, cancellationToken); // Submit the easy request to the multi agent. if (request.Content != null) { // If there is request content to be sent, preload the stream // and submit the request to the multi agent. This is separated // out into a separate async method to avoid associated overheads // in the case where there is no request content stream. return QueueOperationWithRequestContentAsync(easy); } else { // Otherwise, just submit the request. ConfigureAndQueue(easy); return easy.Task; } }
private void FinishRequest(EasyRequest completedOperation, int messageResult) { VerboseTrace("messageResult: " + messageResult, easy: completedOperation); if (completedOperation._responseMessage.StatusCode != HttpStatusCode.Unauthorized && completedOperation._handler.PreAuthenticate) { ulong availedAuth; if (Interop.libcurl.curl_easy_getinfo(completedOperation._easyHandle, CURLINFO.CURLINFO_HTTPAUTH_AVAIL, out availedAuth) == CURLcode.CURLE_OK) { // TODO: fix locking in AddCredentialToCache completedOperation._handler.AddCredentialToCache( completedOperation._requestMessage.RequestUri, availedAuth, completedOperation._networkCredential); } // Ignore errors: no need to fail for the sake of putting the credentials into the cache } switch (messageResult) { case CURLcode.CURLE_OK: completedOperation.EnsureResponseMessagePublished(); break; default: completedOperation.FailRequest(CreateHttpRequestException(new CurlException(messageResult, isMulti: false))); break; } // At this point, we've completed processing the entire request, either due to error // or due to completing the entire response. completedOperation.Cleanup(); }
private static void SetCurlCallbacks(EasyRequest easy, IntPtr easyGCHandle) { // Add callback for processing headers easy.SetCurlOption(CURLoption.CURLOPT_HEADERFUNCTION, s_receiveHeadersCallback); easy.SetCurlOption(CURLoption.CURLOPT_HEADERDATA, easyGCHandle); // If we're sending data as part of the request, add callbacks for sending request data if (easy._requestMessage.Content != null) { easy.SetCurlOption(CURLoption.CURLOPT_READFUNCTION, s_sendCallback); easy.SetCurlOption(CURLoption.CURLOPT_READDATA, easyGCHandle); easy.SetCurlOption(CURLoption.CURLOPT_SEEKFUNCTION, s_seekCallback); easy.SetCurlOption(CURLoption.CURLOPT_SEEKDATA, easyGCHandle); } // If we're expecting any data in response, add a callback for receiving body data if (easy._requestMessage.Method != HttpMethod.Head) { easy.SetCurlOption(CURLoption.CURLOPT_WRITEFUNCTION, s_receiveBodyCallback); easy.SetCurlOption(CURLoption.CURLOPT_WRITEDATA, easyGCHandle); } }
private static bool TryParseStatusLine(HttpResponseMessage response, string responseHeader, EasyRequest state) { if (!responseHeader.StartsWith(HttpPrefix, StringComparison.OrdinalIgnoreCase)) { return false; } // Clear the header if status line is recieved again. This signifies that there are multiple response headers (like in redirection). response.Headers.Clear(); response.Content.Headers.Clear(); int responseHeaderLength = responseHeader.Length; // Check if line begins with HTTP/1.1 or HTTP/1.0 int prefixLength = HttpPrefix.Length; int versionIndex = prefixLength + 2; if ((versionIndex < responseHeaderLength) && (responseHeader[prefixLength] == '1') && (responseHeader[prefixLength + 1] == '.')) { response.Version = responseHeader[versionIndex] == '1' ? HttpVersion.Version11 : responseHeader[versionIndex] == '0' ? HttpVersion.Version10 : new Version(0, 0); } else { response.Version = new Version(0, 0); } // TODO: Parsing errors are treated as fatal. Find right behaviour int spaceIndex = responseHeader.IndexOf(SpaceChar); if (spaceIndex > -1) { int codeStartIndex = spaceIndex + 1; int statusCode = 0; // Parse first 3 characters after a space as status code if (TryParseStatusCode(responseHeader, codeStartIndex, out statusCode)) { response.StatusCode = (HttpStatusCode)statusCode; // 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 ((response.StatusCode == HttpStatusCode.Redirect) && !(state._handler.Credentials is CredentialCache)) { state.SetCurlOption(CURLoption.CURLOPT_HTTPAUTH, CURLAUTH.None); state.SetCurlOption(CURLoption.CURLOPT_USERNAME, IntPtr.Zero); state.SetCurlOption(CURLoption.CURLOPT_PASSWORD, IntPtr.Zero); state._networkCredential = null; } int codeEndIndex = codeStartIndex + StatusCodeLength; int reasonPhraseIndex = codeEndIndex + 1; if (reasonPhraseIndex < responseHeaderLength && responseHeader[codeEndIndex] == SpaceChar) { int newLineCharIndex = responseHeader.IndexOfAny(s_newLineCharArray, reasonPhraseIndex); int reasonPhraseEnd = newLineCharIndex >= 0 ? newLineCharIndex : responseHeaderLength; response.ReasonPhrase = responseHeader.Substring(reasonPhraseIndex, reasonPhraseEnd - reasonPhraseIndex); } } } return true; }
private static bool TryParseStatusLine(HttpResponseMessage response, string responseHeader, EasyRequest state) { if (!responseHeader.StartsWith(HttpPrefix, StringComparison.OrdinalIgnoreCase)) { return false; } // Clear the header if status line is recieved again. This signifies that there are multiple response headers (like in redirection). response.Headers.Clear(); response.Content.Headers.Clear(); int responseHeaderLength = responseHeader.Length; // Check if line begins with HTTP/1.1 or HTTP/1.0 int prefixLength = HttpPrefix.Length; int versionIndex = prefixLength + 2; if ((versionIndex < responseHeaderLength) && (responseHeader[prefixLength] == '1') && (responseHeader[prefixLength + 1] == '.')) { response.Version = responseHeader[versionIndex] == '1' ? HttpVersion.Version11 : responseHeader[versionIndex] == '0' ? HttpVersion.Version10 : new Version(0, 0); } else { response.Version = new Version(0, 0); } // TODO: Parsing errors are treated as fatal. Find right behaviour int spaceIndex = responseHeader.IndexOf(SpaceChar); if (spaceIndex > -1) { int codeStartIndex = spaceIndex + 1; int statusCode = 0; // Parse first 3 characters after a space as status code if (TryParseStatusCode(responseHeader, codeStartIndex, out statusCode)) { response.StatusCode = (HttpStatusCode)statusCode; int codeEndIndex = codeStartIndex + StatusCodeLength; int reasonPhraseIndex = codeEndIndex + 1; if (reasonPhraseIndex < responseHeaderLength && responseHeader[codeEndIndex] == SpaceChar) { int newLineCharIndex = responseHeader.IndexOfAny(s_newLineCharArray, reasonPhraseIndex); int reasonPhraseEnd = newLineCharIndex >= 0 ? newLineCharIndex : responseHeaderLength; response.ReasonPhrase = responseHeader.Substring(reasonPhraseIndex, reasonPhraseEnd - reasonPhraseIndex); } state._isRedirect = state._handler.AutomaticRedirection && (response.StatusCode == HttpStatusCode.Redirect || response.StatusCode == HttpStatusCode.RedirectKeepVerb || response.StatusCode == HttpStatusCode.RedirectMethod) ; } } return true; }
private void VerboseTraceIf(bool condition, string text = null, [CallerMemberName] string memberName = null, EasyRequest easy = null) { if (condition) { CurlHandler.VerboseTrace(text, memberName, easy, agent: this); } }
protected internal override Task<HttpResponseMessage> SendAsync( HttpRequestMessage request, CancellationToken cancellationToken) { if (request == null) { throw new ArgumentNullException("request", SR.net_http_handler_norequest); } if (request.RequestUri.Scheme == UriSchemeHttps) { if (!s_supportsSSL) { throw new PlatformNotSupportedException(SR.net_http_unix_https_support_unavailable_libcurl); } } else { Debug.Assert(request.RequestUri.Scheme == UriSchemeHttp, "HttpClient expected to validate scheme as http or https."); } if (request.Headers.TransferEncodingChunked.GetValueOrDefault() && (request.Content == null)) { throw new InvalidOperationException(SR.net_http_chunked_not_allowed_with_empty_content); } if (_useCookie && _cookieContainer == null) { throw new InvalidOperationException(SR.net_http_invalid_cookiecontainer); } CheckDisposed(); SetOperationStarted(); // Do an initial cancellation check to avoid initiating the async operation if // cancellation has already been requested. After this, we'll rely on CancellationToken.Register // to notify us of cancellation requests and shut down the operation if possible. if (cancellationToken.IsCancellationRequested) { return Task.FromCanceled<HttpResponseMessage>(cancellationToken); } // Create the easy request. This associates the easy request with this handler and configures // it based on the settings configured for the handler. var easy = new EasyRequest(this, request, cancellationToken); try { easy.InitializeCurl(); if (easy._requestContentStream != null) { easy._requestContentStream.Run(); } _agent.Queue(new MultiAgent.IncomingRequest { Easy = easy, Type = MultiAgent.IncomingRequestType.New }); } catch (Exception exc) { easy.FailRequest(exc); easy.Cleanup(); // no active processing remains, so we can cleanup } return easy.Task; }
private void DeactivateActiveRequest( SafeCurlMultiHandle multiHandle, EasyRequest easy, IntPtr gcHandlePtr, CancellationTokenRegistration cancellationRegistration) { // Remove the operation from the multi handle so we can shut down the multi handle cleanly int removeResult = Interop.libcurl.curl_multi_remove_handle(multiHandle, easy._easyHandle); Debug.Assert(removeResult == CURLMcode.CURLM_OK, "Failed to remove easy handle"); // ignore cleanup errors in release // Release the associated GCHandle so that it's not kept alive forever if (gcHandlePtr != IntPtr.Zero) { try { GCHandle.FromIntPtr(gcHandlePtr).Free(); _activeOperations.Remove(gcHandlePtr); } catch (InvalidOperationException) { Debug.Fail("Couldn't get/free the GCHandle for an active operation while shutting down due to failure"); } } // Undo cancellation registration cancellationRegistration.Dispose(); }
private static void VerboseTrace(string text = null, [CallerMemberName] string memberName = null, EasyRequest easy = null, MultiAgent agent = null) { // If we weren't handed a multi agent, see if we can get one from the EasyRequest if (agent == null && easy != null && easy._associatedMultiAgent != null) { agent = easy._associatedMultiAgent; } int? agentId = agent != null ? agent.RunningWorkerId : null; // Get an ID string that provides info about which MultiAgent worker and which EasyRequest this trace is about string ids = ""; if (agentId != null || easy != null) { ids = "(" + (agentId != null ? "M#" + agentId : "") + (agentId != null && easy != null ? ", " : "") + (easy != null ? "E#" + easy.Task.Id : "") + ")"; } // Create the message and trace it out string msg = string.Format("[{0, -30}]{1, -16}: {2}", memberName, ids, text); Interop.Sys.PrintF("%s\n", msg); }
private static void VerboseTraceIf(bool condition, string text = null, [CallerMemberName] string memberName = null, EasyRequest easy = null) { if (condition) { VerboseTrace(text, memberName, easy, agent: null); } }
private static bool TryParseStatusLine(HttpResponseMessage response, string responseHeader, EasyRequest state) { if (!responseHeader.StartsWith(CurlResponseParseUtils.HttpPrefix, StringComparison.OrdinalIgnoreCase)) { return false; } // Clear the header if status line is recieved again. This signifies that there are multiple response headers (like in redirection). response.Headers.Clear(); response.Content.Headers.Clear(); CurlResponseParseUtils.ReadStatusLine(response, responseHeader); state._isRedirect = state._handler.AutomaticRedirection && (response.StatusCode == HttpStatusCode.Redirect || response.StatusCode == HttpStatusCode.RedirectKeepVerb || response.StatusCode == HttpStatusCode.RedirectMethod) ; return true; }
private void VerboseTrace(string text = null, [CallerMemberName] string memberName = null, EasyRequest easy = null) { CurlHandler.VerboseTrace(text, memberName, easy, agent: this); }
private static void HandleRedirectLocationHeader(EasyRequest state, string locationValue) { Debug.Assert(state._isRedirect); Debug.Assert(state._handler.AutomaticRedirection); string location = locationValue.Trim(); //only for absolute redirects Uri forwardUri; if (Uri.TryCreate(location, UriKind.RelativeOrAbsolute, out forwardUri) && forwardUri.IsAbsoluteUri) { KeyValuePair<NetworkCredential, CURLAUTH> ncAndScheme = GetCredentials(state._handler.Credentials as CredentialCache, forwardUri); if (ncAndScheme.Key != null) { state.SetCredentialsOptions(ncAndScheme); } else { state.SetCurlOption(CURLoption.CURLOPT_USERNAME, IntPtr.Zero); state.SetCurlOption(CURLoption.CURLOPT_PASSWORD, IntPtr.Zero); } // reset proxy - it is possible that the proxy has different credentials for the new URI state.SetProxyOptions(forwardUri); if (state._handler._useCookie) { // reset cookies. state.SetCurlOption(CURLoption.CURLOPT_COOKIE, IntPtr.Zero); // set cookies again state.SetCookieOption(forwardUri); } } }