using System.Text.RegularExpressions; using System.Net; using RazorLight; namespace MeetingAssistant.Workflow; internal sealed class MeetingWorkflowTemplateRenderer { private static readonly Regex EmailAddressPattern = new( @"(?[A-Za-z0-9.!#$%&'*+/=?^_`{|}~-]+)@(?(?:[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?\.)+[A-Za-z]{2,63})(?![A-Za-z0-9-])", RegexOptions.Compiled | RegexOptions.CultureInvariant | RegexOptions.IgnoreCase); private readonly RazorLightEngine razorEngine = MeetingWorkflowRazorEngineFactory.Create(); public async Task RenderAsync( string template, MeetingWorkflowTemplateModel model) { if (!ContainsRazorTemplateSyntax(template)) { return template; } var rendered = await razorEngine.CompileRenderStringAsync( Guid.NewGuid().ToString("N"), EscapeEmailAddressAtSigns(template), model); return WebUtility.HtmlDecode(rendered); } public static bool ContainsRazorTemplateSyntax(string value) { if (!value.Contains('@', StringComparison.Ordinal)) { return false; } var emailAtSigns = EmailAddressPattern .Matches(value) .Select(match => match.Index + match.Value.IndexOf('@', StringComparison.Ordinal)) .ToHashSet(); for (var index = 0; index < value.Length; index++) { if (value[index] != '@') { continue; } if (index + 1 < value.Length && value[index + 1] == '@') { index++; continue; } if (emailAtSigns.Contains(index)) { continue; } return true; } return false; } private static string EscapeEmailAddressAtSigns(string value) { if (!value.Contains('@', StringComparison.Ordinal)) { return value; } return EmailAddressPattern.Replace( value, match => match.Value.Replace("@", "@@", StringComparison.Ordinal)); } }