Files
AI-Studio/app/MindWork AI Studio/Tools/Mail/MailAttachmentRules.cs
T
Thorsten Sommer c4400c0ff6
Build and Release / Determine run mode (push) Waiting to run
Build and Release / Read metadata (push) Blocked by required conditions
Build and Release / Sync Flatpak repo (push) Blocked by required conditions
Build and Release / Collect Flatpak artifacts (push) Blocked by required conditions
Build and Release / Verify (push) Waiting to run
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-pc-windows-msvc.exe, win-arm64, windows-latest, aarch64-pc-windows-msvc, nsis,updater, nsis) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-unknown-linux-gnu, linux-arm64, ubuntu-22.04-arm, aarch64-unknown-linux-gnu, appimage,updater, appimage) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-apple-darwin, osx-x64, macos-latest, x86_64-apple-darwin, dmg,app,updater, dmg) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-pc-windows-msvc.exe, win-x64, windows-latest, x86_64-pc-windows-msvc, nsis,updater, nsis) (push) Blocked by required conditions
Build and Release / Prepare & create release (push) Blocked by required conditions
Build and Release / Publish release (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-apple-darwin, osx-arm64, macos-latest, aarch64-apple-darwin, dmg,app,updater, dmg) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-unknown-linux-gnu, linux-x64, ubuntu-22.04, x86_64-unknown-linux-gnu, appimage,updater, appimage) (push) Blocked by required conditions
Added mailboxes as local data sources (#1022)
2026-10-04 12:26:26 +02:00

105 lines
4.8 KiB
C#

using AIStudio.Settings.DataModel;
using AIStudio.Tools.Rust;
using AIStudio.Tools.Validation;
using MailKit;
using MimeKit;
namespace AIStudio.Tools.Mail;
/// <summary>
/// Decides which attachments of a mail count as such, and which of them get their text read.
/// </summary>
/// <remarks>
/// Everything here works on the structure of a mail, so nothing of an attachment is fetched before
/// it is clear that its text is wanted. Read are documents alone, the same types a local folder
/// indexes. Whatever else a mail carries, e.g. images or archives, is known by its name only.
/// </remarks>
public static class MailAttachmentRules
{
private const string BASE64_ENCODING = "base64";
private const long BYTES_PER_MEGABYTE = 1024L * 1024L;
/// <summary>
/// Whether a part is the signature of a signed mail rather than something the sender attached.
/// </summary>
/// <remarks>
/// A signed mail carries its signature as a part of its own, which many programs mark as an
/// attachment. A reader never sees it as one, since a mail program shows a seal instead, and it
/// has no text to read.
/// </remarks>
/// <param name="contentType">The content type of the part.</param>
public static bool IsSignature(ContentType contentType) =>
contentType.IsMimeType("application", "pkcs7-signature")
|| contentType.IsMimeType("application", "x-pkcs7-signature")
|| contentType.IsMimeType("application", "pgp-signature");
/// <summary>
/// Up to which size the text of an attachment is read.
/// </summary>
/// <remarks>
/// A size beyond what the dialog allows, e.g. from a settings file written by hand, is brought
/// back into that range. Attachments are a cost of their own, and no value should open them up
/// without bounds.
/// </remarks>
/// <param name="mailbox">The mailbox.</param>
/// <returns>The size in megabytes, or null when the mailbox reads no attachments at all.</returns>
public static int? GetMaxSizeMegabytes(DataSourceMailbox mailbox) => mailbox.IndexAttachments
? Math.Clamp(mailbox.MaxAttachmentSizeMegabytes, DataSourceValidation.MIN_ATTACHMENT_SIZE_MEGABYTES, DataSourceValidation.MAX_ATTACHMENT_SIZE_MEGABYTES)
: null;
/// <summary>
/// The file type of an attachment, from its name or else from its content type.
/// </summary>
/// <remarks>
/// Only ever used after checking it against the known document types, which is what keeps a
/// name of the sender's choosing from becoming part of a path.
/// </remarks>
/// <param name="part">The attachment.</param>
/// <returns>The extension in lower case and without a dot, or empty when there is none.</returns>
public static string GetExtension(BodyPartBasic part)
{
var extension = Path.GetExtension(MailTextNormalization.NormalizeHeaderValue(part.FileName)).TrimStart('.');
if (extension.Length is 0 && MimeTypes.TryGetExtension(part.ContentType.MimeType, out var contentTypeExtension))
extension = contentTypeExtension.TrimStart('.');
return extension.ToLowerInvariant();
}
/// <summary>
/// How large an attachment is once it is decoded, as far as the structure of the mail tells.
/// </summary>
/// <remarks>
/// The server reports the size as the attachment travels, which with Base64 is a third more than
/// the file the user sees. The estimate errs a little to the large side, since it counts the line
/// breaks of the encoding as well.
/// </remarks>
/// <param name="part">The attachment.</param>
/// <returns>The estimated size in bytes.</returns>
public static long EstimateDecodedSize(BodyPartBasic part) => string.Equals(part.ContentTransferEncoding, BASE64_ENCODING, StringComparison.OrdinalIgnoreCase)
? part.Octets / 4L * 3L
: part.Octets;
/// <summary>
/// Why the text of an attachment is not read.
/// </summary>
/// <param name="part">The attachment.</param>
/// <param name="maxSizeMegabytes">Up to which size attachments are read, or null when none are, cf. GetMaxSizeMegabytes.</param>
/// <returns>The reason, or null when the text is to be read.</returns>
public static MailPartTextState? GetReasonToSkip(BodyPartBasic part, int? maxSizeMegabytes)
{
if (maxSizeMegabytes is not { } maxSize)
return MailPartTextState.ATTACHMENTS_DISABLED;
if (!FileTypes.IsAllowedExtension(GetExtension(part), FileTypes.DOCUMENT))
return MailPartTextState.UNSUPPORTED_TYPE;
// Beyond what can be fetched at all, an attachment is too large whatever the limit:
if (EstimateDecodedSize(part) > maxSize * BYTES_PER_MEGABYTE || part.Octets > MailAttachmentPieces.MAX_OCTETS)
return MailPartTextState.TOO_LARGE;
return null;
}
}