Files
MeWeb/src/MeWeb.Core/Checks/ServerProbe.cs
T

120 lines
3.8 KiB
C#
Raw Normal View History

using System.Net;
using System.Net.Http.Json;
using System.Net.Sockets;
using MeWeb.Shared;
namespace MeWeb.Core.Checks;
public enum OnlineState
{
Online,
Captive,
Rewritten,
NoNames,
Offline,
}
public class OnlineReport
{
public OnlineState State { get; set; }
public ViewInfo Server { get; set; }
public string Portal { get; set; }
}
public class ServerProbe(HttpClient https, HttpClient plain)
{
static readonly TimeSpan Patience = TimeSpan.FromSeconds(6);
public async Task<OnlineReport> Online(CancellationToken ct)
{
var names = Resolves(Endpoints.Host, ct);
var info = Info(ct);
var web = Plain(ct);
await Task.WhenAll(names, info, web);
var (plainClean, portal) = web.Result;
if (info.Result is not null)
return new OnlineReport { State = plainClean is false ? OnlineState.Rewritten : OnlineState.Online, Server = info.Result, Portal = portal };
if (plainClean is false) return new OnlineReport { State = OnlineState.Captive, Portal = portal };
return new OnlineReport { State = names.Result ? OnlineState.Offline : OnlineState.NoNames };
}
public async Task<string> Echo(bool v6, CancellationToken ct)
{
try
{
using var timeout = CancellationTokenSource.CreateLinkedTokenSource(ct);
timeout.CancelAfter(Patience);
var origin = v6 ? Endpoints.BaseV6 : Endpoints.BaseV4;
var answer = await https.GetFromJsonAsync(origin + Endpoints.Server.GET_Ip, MeWebJsonContext.Default.ViewIp, timeout.Token);
return answer?.Address;
}
catch (Exception ex) when (ex is HttpRequestException or TaskCanceledException or System.Text.Json.JsonException && !ct.IsCancellationRequested)
{
return null;
}
}
public static async Task<IPAddress> ResolveV4(CancellationToken ct)
{
try
{
var addresses = await Dns.GetHostAddressesAsync(Endpoints.HostV4, AddressFamily.InterNetwork, ct);
return addresses.FirstOrDefault();
}
catch (SocketException)
{
return null;
}
}
static async Task<bool> Resolves(string host, CancellationToken ct)
{
try
{
return (await Dns.GetHostAddressesAsync(host, ct)).Length > 0;
}
catch (SocketException)
{
return false;
}
}
async Task<ViewInfo> Info(CancellationToken ct)
{
try
{
using var timeout = CancellationTokenSource.CreateLinkedTokenSource(ct);
timeout.CancelAfter(Patience);
return await https.GetFromJsonAsync(Endpoints.Base + Endpoints.Server.GET_Info, MeWebJsonContext.Default.ViewInfo, timeout.Token);
}
catch (Exception ex) when (ex is HttpRequestException or TaskCanceledException or System.Text.Json.JsonException && !ct.IsCancellationRequested)
{
return null;
}
}
async Task<(bool? Clean, string Portal)> Plain(CancellationToken ct)
{
try
{
using var timeout = CancellationTokenSource.CreateLinkedTokenSource(ct);
timeout.CancelAfter(Patience);
using var response = await plain.GetAsync($"http://{Endpoints.Host}{Endpoints.Server.GET_Info}", HttpCompletionOption.ResponseHeadersRead, timeout.Token);
var location = response.Headers.Location;
if ((int)response.StatusCode is >= 300 and < 400 && location is not null)
{
var target = location.IsAbsoluteUri ? location : new Uri(new Uri($"http://{Endpoints.Host}"), location);
var clean = target.Scheme == Uri.UriSchemeHttps && string.Equals(target.Host, Endpoints.Host, StringComparison.OrdinalIgnoreCase);
return (clean, clean ? null : target.Host);
}
if (response.IsSuccessStatusCode && response.Content.Headers.ContentType?.MediaType == "application/json") return (true, null);
return (false, null);
}
catch (Exception ex) when (ex is HttpRequestException or TaskCanceledException && !ct.IsCancellationRequested)
{
return (null, null);
}
}
}