using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using System; using System.ComponentModel.DataAnnotations; using System.Linq; using System.Net.Http; using System.Net.Http.Headers; using System.Threading; using System.Threading.Tasks; namespace FastGithub.Scanner.ScanMiddlewares { /// /// https扫描中间件 /// [Service(ServiceLifetime.Singleton)] sealed class HttpsScanMiddleware : IMiddleware { private readonly IOptionsMonitor options; private readonly IHttpClientFactory httpClientFactory; private readonly ILogger logger; /// /// https扫描中间件 /// /// /// public HttpsScanMiddleware( IOptionsMonitor options, IHttpClientFactory httpClientFactory, ILogger logger) { this.options = options; this.httpClientFactory = httpClientFactory; this.logger = logger; } /// /// https扫描 /// /// /// /// public async Task InvokeAsync(GithubContext context, Func next) { try { context.Available = false; var request = new HttpRequestMessage { Method = HttpMethod.Head, RequestUri = new Uri($"https://{context.Address}"), }; request.Headers.Host = context.Domain; var timeout = this.options.CurrentValue.Timeout; using var timeoutTokenSource = new CancellationTokenSource(timeout); using var linkedTokenSource = CancellationTokenSource.CreateLinkedTokenSource(timeoutTokenSource.Token, context.CancellationToken); var httpClient = this.httpClientFactory.CreateClient(nameof(FastGithub)); using var response = await httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, linkedTokenSource.Token); VerifyHttpsResponse(context.Domain, response); context.Available = true; await next(); } catch (Exception ex) { context.CancellationToken.ThrowIfCancellationRequested(); this.logger.LogTrace($"{context.Domain} {context.Address} { GetInnerMessage(ex)}"); } } /// /// 验证响应内容 /// /// /// /// /// private static void VerifyHttpsResponse(string domain, HttpResponseMessage response) { response.EnsureSuccessStatusCode(); if (domain == "github.com" || domain.EndsWith(".github.com")) { if (response.Headers.Server.Any(item => IsGithubServer(item)) == false) { throw new ValidationException("伪造的github服务"); } } static bool IsGithubServer(ProductInfoHeaderValue headerValue) { var value = headerValue.Product?.Name; return string.Equals("github.com", value, StringComparison.OrdinalIgnoreCase); } } private static string GetInnerMessage(Exception ex) { while (ex.InnerException != null) { return GetInnerMessage(ex.InnerException); } return ex.Message; } } }