using System.Net.Http.Headers; namespace ServiceLib.Services; /// /// Download /// public class DownloadService { public event EventHandler? UpdateCompleted; public event ErrorEventHandler? Error; private static readonly string _tag = "DownloadService"; /// /// Downloads data with the specified proxy and reports progress messages. /// public async Task DownloadDataAsync(string url, IWebProxy webProxy, int downloadTimeout, Func updateFunc) { try { var progress = new Progress(); progress.ProgressChanged += (sender, value) => updateFunc?.Invoke(false, $"{value}"); await DownloaderHelper.Instance.DownloadDataAsync4Speed(webProxy, url, progress, downloadTimeout); } catch (Exception ex) { await updateFunc?.Invoke(false, ex.Message); if (ex.InnerException != null) { await updateFunc?.Invoke(false, ex.InnerException.Message); } } return 0; } /// /// Downloads a file and reports progress through events. /// public async Task DownloadFileAsync(string url, string fileName, bool blProxy, int downloadTimeout) { try { UpdateCompleted?.Invoke(this, new UpdateResult(false, $"{ResUI.Downloading} {url}")); var progress = new Progress(); progress.ProgressChanged += (sender, value) => UpdateCompleted?.Invoke(this, new UpdateResult(value > 100, $"...{value}%")); var webProxy = await GetWebProxy(blProxy); await DownloaderHelper.Instance.DownloadFileAsync(webProxy, url, fileName, progress, downloadTimeout); } catch (Exception ex) { Logging.SaveLog(_tag, ex); Error?.Invoke(this, new ErrorEventArgs(ex)); if (ex.InnerException != null) { Error?.Invoke(this, new ErrorEventArgs(ex.InnerException)); } } } /// /// Gets redirect target URL without following redirects automatically. /// public async Task UrlRedirectAsync(string url, bool blProxy) { var webRequestHandler = new SocketsHttpHandler { AllowAutoRedirect = false, Proxy = await GetWebProxy(blProxy) }; var certificateChainPolicy = CertPemManager.Instance.BuildCertificateChainPolicy(); if (certificateChainPolicy != null) { webRequestHandler.SslOptions.CertificateChainPolicy = certificateChainPolicy; webRequestHandler.SslOptions.RemoteCertificateValidationCallback = null; } using var client = new HttpClient(webRequestHandler); var response = await client.GetAsync(url); if (response.StatusCode == HttpStatusCode.Redirect && response.Headers.Location is not null) { return response.Headers.Location.ToString(); } else { Error?.Invoke(this, new ErrorEventArgs(new Exception("StatusCode error: " + response.StatusCode))); Logging.SaveLog("StatusCode error: " + url); return null; } } /// /// Tries to download string content using proxy switch setting. /// public async Task TryDownloadString(string url, bool blProxy, string userAgent) { var webProxy = await GetWebProxy(blProxy); return await TryDownloadString(url, webProxy, userAgent); } /// /// Tries to download string content with a specified proxy. /// public async Task TryDownloadString(string url, IWebProxy? webProxy, string userAgent) { var timeout = 15; try { var result1 = await DownloadStringAsync(url, webProxy, userAgent, timeout); if (result1.IsNotEmpty()) { return result1; } } catch (Exception ex) { Logging.SaveLog(_tag, ex); Error?.Invoke(this, new ErrorEventArgs(ex)); if (ex.InnerException != null) { Error?.Invoke(this, new ErrorEventArgs(ex.InnerException)); } } try { var result2 = await DownloadStringViaDownloader(url, webProxy, userAgent, timeout); if (result2.IsNotEmpty()) { return result2; } } catch (Exception ex) { Logging.SaveLog(_tag, ex); Error?.Invoke(this, new ErrorEventArgs(ex)); if (ex.InnerException != null) { Error?.Invoke(this, new ErrorEventArgs(ex.InnerException)); } } return null; } /// /// Downloads string content via HttpClient. /// private async Task DownloadStringAsync(string url, IWebProxy? webProxy, string userAgent, int timeout) { try { var connectTimeout = Math.Clamp(timeout / 5, 2, 5); var handler = new SocketsHttpHandler { Proxy = webProxy, UseProxy = webProxy != null, ConnectTimeout = TimeSpan.FromSeconds(connectTimeout) }; var certificateChainPolicy = CertPemManager.Instance.BuildCertificateChainPolicy(); if (certificateChainPolicy != null) { handler.SslOptions.CertificateChainPolicy = certificateChainPolicy; handler.SslOptions.RemoteCertificateValidationCallback = null; } using var client = new HttpClient(handler) { Timeout = Timeout.InfiniteTimeSpan }; if (userAgent.IsNullOrEmpty()) { userAgent = Utils.GetVersion(false); } client.DefaultRequestHeaders.UserAgent.TryParseAdd(userAgent); Uri uri = new(url); //Authorization Header if (uri.UserInfo.IsNotEmpty()) { client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", Utils.Base64Encode(uri.UserInfo)); } using var cts = new CancellationTokenSource(); cts.CancelAfter(TimeSpan.FromSeconds(timeout)); return await client.GetStringAsync(url, cts.Token); } catch (Exception ex) { Logging.SaveLog(_tag, ex); Error?.Invoke(this, new ErrorEventArgs(ex)); if (ex.InnerException != null) { Error?.Invoke(this, new ErrorEventArgs(ex.InnerException)); } } return null; } /// /// Downloads string content via DownloaderHelper. /// private async Task DownloadStringViaDownloader(string url, IWebProxy? webProxy, string userAgent, int timeout) { try { if (userAgent.IsNullOrEmpty()) { userAgent = Utils.GetVersion(false); } var result = await DownloaderHelper.Instance.DownloadStringAsync(webProxy, url, userAgent, timeout); return result; } catch (Exception ex) { Logging.SaveLog(_tag, ex); Error?.Invoke(this, new ErrorEventArgs(ex)); if (ex.InnerException != null) { Error?.Invoke(this, new ErrorEventArgs(ex.InnerException)); } } return null; } /// /// Creates local SOCKS proxy when proxy switch is enabled. /// private async Task GetWebProxy(bool blProxy) { if (!blProxy) { return null; } var port = AppManager.Instance.GetLocalPort(EInboundProtocol.socks); if (await SocketCheck(Global.Loopback, port) == false) { return null; } return new WebProxy($"socks5://{Global.Loopback}:{port}"); } /// /// Checks whether the specified TCP endpoint is reachable. /// private async Task SocketCheck(string ip, int port) { try { IPEndPoint point = new(IPAddress.Parse(ip), port); using Socket? sock = new(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); await sock.ConnectAsync(point); return true; } catch (Exception) { return false; } } }