#if !UNITY_WEBGL || UNITY_EDITOR using System; using System.Collections.Generic; using System.IO; #if !NETFX_CORE || UNITY_EDITOR using System.Net.Security; #endif #if !BESTHTTP_DISABLE_ALTERNATE_SSL && (!UNITY_WEBGL || UNITY_EDITOR) using BestHTTP.SecureProtocol.Org.BouncyCastle.Tls; using BestHTTP.Connections.TLS; using System.Threading; #endif #if NETFX_CORE using System.Threading.Tasks; using Windows.Networking.Sockets; using TcpClient = BestHTTP.PlatformSupport.TcpClient.WinRT.TcpClient; //Disable CD4014: Because this call is not awaited, execution of the current method continues before the call is completed. Consider applying the 'await' operator to the result of the call. #pragma warning disable 4014 #else using TcpClient = BestHTTP.PlatformSupport.TcpClient.General.TcpClient; using System.Security.Cryptography.X509Certificates; #endif using BestHTTP.Timings; namespace BestHTTP.Connections { public sealed class TCPConnector : IDisposable { public bool IsConnected { get { return this.Client != null && this.Client.Connected; } } public string NegotiatedProtocol { get; private set; } public TcpClient Client { get; private set; } public Stream TopmostStream { get; private set; } public Stream Stream { get; private set; } public bool LeaveOpen { get; set; } public void Connect(HTTPRequest request) { string negotiatedProtocol = HTTPProtocolFactory.W3C_HTTP1; Uri uri = #if !BESTHTTP_DISABLE_PROXY request.HasProxy ? request.Proxy.Address : #endif request.CurrentUri; #region TCP Connection if (Client == null) Client = new TcpClient(); if (!Client.Connected) { Client.ConnectTimeout = request.ConnectTimeout; #if NETFX_CORE Client.UseHTTPSProtocol = #if !BESTHTTP_DISABLE_ALTERNATE_SSL && (!UNITY_WEBGL || UNITY_EDITOR) !Request.UseAlternateSSL && #endif HTTPProtocolFactory.IsSecureProtocol(uri); #endif if (HTTPManager.Logger.Level == Logger.Loglevels.All) HTTPManager.Logger.Verbose("TCPConnector", string.Format("'{0}' - Connecting to {1}:{2}", request.CurrentUri.ToString(), uri.Host, uri.Port.ToString()), request.Context); #if !NETFX_CORE && (!UNITY_WEBGL || UNITY_EDITOR) bool changed = false; int? sendBufferSize = null, receiveBufferSize = null; if (HTTPManager.SendBufferSize.HasValue) { sendBufferSize = Client.SendBufferSize; Client.SendBufferSize = HTTPManager.SendBufferSize.Value; changed = true; } if (HTTPManager.ReceiveBufferSize.HasValue) { receiveBufferSize = Client.ReceiveBufferSize; Client.ReceiveBufferSize = HTTPManager.ReceiveBufferSize.Value; changed = true; } if (HTTPManager.Logger.Level == Logger.Loglevels.All) { if (changed) HTTPManager.Logger.Verbose("TCPConnector", string.Format("'{0}' - Buffer sizes changed - Send from: {1} to: {2}, Receive from: {3} to: {4}, Blocking: {5}", request.CurrentUri.ToString(), sendBufferSize, Client.SendBufferSize, receiveBufferSize, Client.ReceiveBufferSize, Client.Client.Blocking), request.Context); else HTTPManager.Logger.Verbose("TCPConnector", string.Format("'{0}' - Buffer sizes - Send: {1} Receive: {2} Blocking: {3}", request.CurrentUri.ToString(), Client.SendBufferSize, Client.ReceiveBufferSize, Client.Client.Blocking), request.Context); } #endif #if NETFX_CORE && !UNITY_EDITOR && !ENABLE_IL2CPP try { Client.Connect(uri.Host, uri.Port); } finally { request.Timing.Add(TimingEventNames.TCP_Connection); } #else System.Net.IPAddress[] addresses = null; try { if (Client.ConnectTimeout > TimeSpan.Zero) { // https://forum.unity3d.com/threads/best-http-released.200006/page-37#post-3150972 using (System.Threading.ManualResetEvent mre = new System.Threading.ManualResetEvent(false)) { IAsyncResult result = System.Net.Dns.BeginGetHostAddresses(uri.Host, (res) => { try { mre.Set(); } catch { } }, null); bool success = mre.WaitOne(Client.ConnectTimeout); if (success) { addresses = System.Net.Dns.EndGetHostAddresses(result); } else { throw new TimeoutException("DNS resolve timed out!"); } } } else { addresses = System.Net.Dns.GetHostAddresses(uri.Host); } } finally { request.Timing.Add(TimingEventNames.DNS_Lookup); } if (HTTPManager.Logger.Level == Logger.Loglevels.All) HTTPManager.Logger.Verbose("TCPConnector", string.Format("'{0}' - DNS Query returned with addresses: {1}", request.CurrentUri.ToString(), addresses != null ? addresses.Length : -1), request.Context); if (request.IsCancellationRequested) throw new Exception("IsCancellationRequested"); try { Client.Connect(addresses, uri.Port, request); } finally { request.Timing.Add(TimingEventNames.TCP_Connection); } if (request.IsCancellationRequested) throw new Exception("IsCancellationRequested"); #endif if (HTTPManager.Logger.Level <= Logger.Loglevels.Information) HTTPManager.Logger.Information("TCPConnector", "Connected to " + uri.Host + ":" + uri.Port.ToString(), request.Context); } else if (HTTPManager.Logger.Level <= Logger.Loglevels.Information) HTTPManager.Logger.Information("TCPConnector", "Already connected to " + uri.Host + ":" + uri.Port.ToString(), request.Context); #endregion if (Stream == null) { bool isSecure = HTTPProtocolFactory.IsSecureProtocol(request.CurrentUri); // set the stream to Client.GetStream() so the proxy, if there's any can use it directly. this.Stream = this.TopmostStream = Client.GetStream(); /*if (Stream.CanTimeout) Stream.ReadTimeout = Stream.WriteTimeout = (int)Request.Timeout.TotalMilliseconds;*/ #if !BESTHTTP_DISABLE_PROXY if (request.HasProxy) { try { request.Proxy.Connect(this.Stream, request); } finally { request.Timing.Add(TimingEventNames.Proxy_Negotiation); } } if (request.IsCancellationRequested) throw new Exception("IsCancellationRequested"); #endif // proxy connect is done, we can set the stream to a buffered one. HTTPProxy requires the raw NetworkStream for HTTPResponse's ReadUnknownSize! this.Stream = this.TopmostStream = new BufferedReadNetworkStream(Client.GetStream(), Math.Max(8 * 1024, HTTPManager.ReceiveBufferSize ?? Client.ReceiveBufferSize)); // We have to use Request.CurrentUri here, because uri can be a proxy uri with a different protocol if (isSecure) { DateTime tlsNegotiationStartedAt = DateTime.Now; #region SSL Upgrade #if !BESTHTTP_DISABLE_ALTERNATE_SSL && (!UNITY_WEBGL || UNITY_EDITOR) if (HTTPManager.UseAlternateSSLDefaultValue) { var handler = new TlsClientProtocol(this.Stream); List protocols = new List(); #if !BESTHTTP_DISABLE_HTTP2 SupportedProtocols protocol = HTTPProtocolFactory.GetProtocolFromUri(request.CurrentUri); if (protocol == SupportedProtocols.HTTP && request.IsKeepAlive) { // http/2 over tls (https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml#alpn-protocol-ids) protocols.Add(ProtocolName.AsUtf8Encoding(HTTPProtocolFactory.W3C_HTTP2)); } #endif protocols.Add(ProtocolName.AsUtf8Encoding(HTTPProtocolFactory.W3C_HTTP1)); AbstractTls13Client tlsClient = null; if (HTTPManager.TlsClientFactory == null) { tlsClient = HTTPManager.DefaultTlsClientFactory(request, protocols); } else { try { tlsClient = HTTPManager.TlsClientFactory(request, protocols); } catch (Exception ex) { HTTPManager.Logger.Exception(nameof(TCPConnector), nameof(HTTPManager.TlsClientFactory), ex, request.Context); } if (tlsClient == null) tlsClient = HTTPManager.DefaultTlsClientFactory(request, protocols); } //tlsClient.LoggingContext = request.Context; handler.Connect(tlsClient); var applicationProtocol = tlsClient.GetNegotiatedApplicationProtocol(); if (!string.IsNullOrEmpty(applicationProtocol)) negotiatedProtocol = applicationProtocol; Stream = handler.Stream; } else #endif { #if !NETFX_CORE SslStream sslStream = null; if (HTTPManager.ClientCertificationProvider == null) sslStream = new SslStream(Client.GetStream(), false, (sender, cert, chain, errors) => { if (HTTPManager.DefaultCertificationValidator != null) return HTTPManager.DefaultCertificationValidator(request, cert, chain, errors); else return true; }); else sslStream = new SslStream(Client.GetStream(), false, (sender, cert, chain, errors) => { if (HTTPManager.DefaultCertificationValidator != null) return HTTPManager.DefaultCertificationValidator(request, cert, chain, errors); else return true; }, (object sender, string targetHost, X509CertificateCollection localCertificates, X509Certificate remoteCertificate, string[] acceptableIssuers) => { return HTTPManager.ClientCertificationProvider(request, targetHost, localCertificates, remoteCertificate, acceptableIssuers); }); if (!sslStream.IsAuthenticated) { #if !BESTHTTP_DISABLE_HTTP2 && !BESTHTTP_DISABLE_ALTERNATE_SSL && false List protocols = new List(); SupportedProtocols protocol = HTTPProtocolFactory.GetProtocolFromUri(request.CurrentUri); if (protocol == SupportedProtocols.HTTP && request.IsKeepAlive) { // http/2 over tls (https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml#alpn-protocol-ids) protocols.Add(new SslApplicationProtocol(HTTPProtocolFactory.W3C_HTTP2)); } protocols.Add(new SslApplicationProtocol(HTTPProtocolFactory.W3C_HTTP1)); SslClientAuthenticationOptions options = new SslClientAuthenticationOptions(); options.ApplicationProtocols = protocols; options.TargetHost = request.CurrentUri.Host; var task = sslStream.AuthenticateAsClientAsync(options, default(System.Threading.CancellationToken)); task.Wait(); try { negotiatedProtocol = System.Text.Encoding.UTF8.GetString(sslStream.NegotiatedApplicationProtocol.Protocol.Span); } catch (Exception ex) { HTTPManager.Logger.Exception(nameof(TCPConnector), "Accessing SslStream's NegotiatedApplicationProtocol throwed an exception, falling back using HTTP/1.1", ex, request.Context); negotiatedProtocol = HTTPProtocolFactory.W3C_HTTP1; } #else sslStream.AuthenticateAsClient(request.CurrentUri.Host); #endif } Stream = sslStream; #else Stream = Client.GetStream(); #endif } #endregion request.Timing.Add(TimingEventNames.TLS_Negotiation, DateTime.Now - tlsNegotiationStartedAt); } } this.NegotiatedProtocol = negotiatedProtocol; } public void Close() { if (Client != null && !this.LeaveOpen) { try { if (Stream != null) Stream.Close(); } catch { } finally { Stream = null; } try { if (TopmostStream != null) TopmostStream.Close(); } catch { } finally { TopmostStream = null; } try { Client.Close(); } catch { } finally { Client = null; } } } public void Dispose() { Close(); } } } #endif