using System.Diagnostics.CodeAnalysis; using AIStudio.Tools.Mail; using AIStudio.Tools.PluginSystem; using Lua; namespace AIStudio.Settings.DataModel; /// /// A mail server of the organization, which a configuration plugin offers for new mailboxes. /// /// /// Live plugin content rather than a stored configuration object: it is a choice in the mailbox /// dialog, not something the user owns. Large organizations run more than one mail system, so a /// plugin may define several, and the ones of all plugins are offered together. Username and /// password stay with each user. /// public sealed record DataMailboxProvider : ILivePluginContent { /// /// The stable ID of the mail server. /// public string Id { get; private init; } = string.Empty; /// /// The ID of the enterprise configuration plugin that provides this mail server. /// public Guid EnterpriseConfigurationPluginId { get; private init; } = Guid.Empty; /// /// The name shown to the user, e.g., the name of the mail system. /// public string Name { get; private init; } = string.Empty; /// /// The host of the IMAP server, as the organization wrote it. /// public string Host { get; private init; } = string.Empty; /// /// The port of the IMAP server. /// public int Port { get; private init; } = MailboxTransportSecurityExtensions.SSL_ON_CONNECT_PORT; /// /// How the connection to the IMAP server is encrypted. /// public MailboxTransportSecurity TransportSecurity { get; private init; } = MailboxTransportSecurity.SSL_ON_CONNECT; /// /// What the user enters as the username, in the words of the organization. Empty when the organization gave no hint. /// public string UsernameHint { get; private init; } = string.Empty; /// /// The page of the organization which explains how to set up the mailbox. Empty when there is none. /// public string HelpUrl { get; private init; } = string.Empty; /// /// Reads a mail server from an entry of the table MAILBOX_PROVIDERS. /// /// /// An entry with an invalid field is dropped as a whole, also when only an optional field is /// invalid. Offering a server with a part of its settings left out would let the user connect /// differently than the organization meant. /// /// The position of the entry, for the log. /// The entry. /// The ID of the configuration plugin which defines the entry. /// The logger to report an invalid entry to. /// The mail server, when the entry is valid. /// True when the entry is valid. public static bool TryParseConfiguration(int index, LuaTable table, Guid configPluginId, ILogger logger, [NotNullWhen(true)] out DataMailboxProvider? provider) { provider = null; if (!table.TryGetValue("Id", out var idValue) || !idValue.TryRead(out var idText) || !Guid.TryParse(idText, out var id)) { logger.LogWarning("The configured mailbox provider {ProviderIndex} does not contain a valid ID. The ID must be a valid GUID.", index); return false; } if (!table.TryGetValue("Name", out var nameValue) || !nameValue.TryRead(out var name) || string.IsNullOrWhiteSpace(name)) { logger.LogWarning("The configured mailbox provider {ProviderIndex} does not contain a valid Name field.", index); return false; } if (!table.TryGetValue("Host", out var hostValue) || !hostValue.TryRead(out var host) || !MailServerHosts.TryGetIdnHost(host, out _)) { logger.LogWarning("The configured mailbox provider {ProviderIndex} does not contain a valid Host field. The host must be a host name or an IP address, without a scheme or a port.", index); return false; } var transportSecurity = MailboxTransportSecurity.SSL_ON_CONNECT; if (table.TryGetValue("TransportSecurity", out var transportSecurityValue) && (!transportSecurityValue.TryRead(out var transportSecurityText) || !EnumNames.TryParse(transportSecurityText, out transportSecurity) || transportSecurity is MailboxTransportSecurity.UNKNOWN)) { logger.LogWarning("The configured mailbox provider {ProviderIndex} does not contain a valid TransportSecurity field. Allowed values are SSL_ON_CONNECT and STARTTLS.", index); return false; } var port = transportSecurity.GetUsualPort() ?? MailboxTransportSecurityExtensions.SSL_ON_CONNECT_PORT; if (table.TryGetValue("Port", out var portValue) && (!portValue.TryRead(out port) || port is < 1 or > 65535)) { logger.LogWarning("The configured mailbox provider {ProviderIndex} does not contain a valid Port field. The port must be a number from 1 to 65535.", index); return false; } var usernameHint = string.Empty; if (table.TryGetValue("UsernameHint", out var usernameHintValue)) { if (!usernameHintValue.TryRead(out var usernameHintText)) { logger.LogWarning("The configured mailbox provider {ProviderIndex} does not contain a valid UsernameHint field. The hint must be a text.", index); return false; } usernameHint = usernameHintText.Trim(); } var helpUrl = string.Empty; if (table.TryGetValue("HelpUrl", out var helpUrlValue)) { if (!helpUrlValue.TryRead(out var helpUrlText) || !Uri.TryCreate(helpUrlText.Trim(), UriKind.Absolute, out var helpUri) || (helpUri.Scheme != Uri.UriSchemeHttps && helpUri.Scheme != Uri.UriSchemeHttp)) { logger.LogWarning("The configured mailbox provider {ProviderIndex} does not contain a valid HelpUrl field. The URL must be an absolute http or https address.", index); return false; } helpUrl = helpUri.OriginalString; } provider = new DataMailboxProvider { Id = id.ToString(), EnterpriseConfigurationPluginId = configPluginId, Name = name.Trim(), Host = host.Trim(), Port = port, TransportSecurity = transportSecurity, UsernameHint = usernameHint, HelpUrl = helpUrl, }; return true; } }