namespace Sunfish.Web;
/// <summary>Tipo de cliente; decide formato de pagina, juego de caracteres y nombres (ver DOC/CLIENTES.md).</summary>
public enum ClientKind { Modern, Classic, Text, Dos, Win16, Mac }
public sealed record ClientProfile(ClientKind Kind, bool Forced)
{
/// <summary>Pagina en modo moderno (UTF-8, CSS); el resto usa el modo clasico.</summary>
public bool IsModern => Kind == ClientKind.Modern;
/// <summary>Navegador de texto: listados como lineas, sin depender de tablas ni imagenes.</summary>
public bool IsText => Kind == ClientKind.Text || Kind == ClientKind.Dos;
/// <summary>DOS y Windows 3.1: solo entienden nombres 8.3 (se les muestran alias).</summary>
public bool UsesShortNames => Kind is ClientKind.Dos or ClientKind.Win16;
/// <summary>Valor para ?p= (se propaga en los enlaces cuando el perfil se forzo).</summary>
public string Code => Kind switch
{
ClientKind.Modern => "modern",
ClientKind.Text => "text",
ClientKind.Dos => "dos",
ClientKind.Win16 => "win16",
ClientKind.Mac => "mac",
_ => "classic",
};
public static bool TryParseCode(string? code, out ClientKind kind)
{
kind = ClientKind.Classic;
switch (code?.ToLowerInvariant())
{
case "modern": kind = ClientKind.Modern; return true;
case "classic": kind = ClientKind.Classic; return true;
case "text": kind = ClientKind.Text; return true;
case "dos": kind = ClientKind.Dos; return true;
case "win16": kind = ClientKind.Win16; return true;
case "mac": kind = ClientKind.Mac; return true;
default: return false;
}
}
/// <summary>
/// Perfil por ?p= (forzado) o por el User-Agent. La lista de firmas es orientativa y se
/// amplia al probar con clientes reales; lo dudoso cae en Classic, que funciona en todos.
/// </summary>
public static ClientProfile Detect(string? userAgent, string? forced)
{
if (TryParseCode(forced, out var kind))
return new ClientProfile(kind, true);
return new ClientProfile(FromUserAgent(userAgent), false);
}
internal static ClientKind FromUserAgent(string? ua)
{
if (string.IsNullOrWhiteSpace(ua))
return ClientKind.Classic; // herramientas HTTP/1.0 y clientes que no se identifican
if (Has(ua, "DOS") || Has(ua, "Arachne") || Has(ua, "DOSLynx"))
return ClientKind.Dos;
if (Has(ua, "Lynx") || Has(ua, "Links") || Has(ua, "w3m"))
return ClientKind.Text;
if (Has(ua, "Win16") || Has(ua, "Windows 3.1") || Has(ua, "Windows 3.11"))
return ClientKind.Win16;
if (IsClassicMac(ua))
return ClientKind.Mac;
if (IsModernAgent(ua))
return ClientKind.Modern;
return ClientKind.Classic;
}
private static bool IsClassicMac(string ua)
{
if (Has(ua, "Mac OS X") || Has(ua, "Intel Mac") || Has(ua, "iPhone") || Has(ua, "iPad"))
return false;
return Has(ua, "Mac_PowerPC") || Has(ua, "Mac_PPC") || Has(ua, "Mac_68000") || Has(ua, "Mac_68K") ||
Has(ua, "MacWeb") || (Has(ua, "Macintosh") && (Has(ua, "PPC") || Has(ua, "68K")));
}
private static bool IsModernAgent(string ua) =>
ua.StartsWith("Mozilla/5.0", StringComparison.Ordinal) &&
(Has(ua, "AppleWebKit/") || Has(ua, "Gecko/") || Has(ua, "Trident/7"));
private static bool Has(string ua, string token) => ua.Contains(token, StringComparison.OrdinalIgnoreCase);
}