mirror of
https://github.com/aliasvault/aliasvault.git
synced 2026-05-17 21:05:48 -04:00
Merge pull request #198 from lanedirt/188-add-email-anchor-tags-translation-to-open-in-new-tabs-instead-of-iframe
Fix email anchor tag target=blank conversion
This commit is contained in:
@@ -77,7 +77,6 @@ public class EmailBoxController(IDbContextFactory<AliasServerDbContext> dbContex
|
||||
{
|
||||
Id = x.Id,
|
||||
Subject = x.Subject,
|
||||
FromDisplay = ConversionHelper.ConvertFromToFromDisplay(x.From),
|
||||
FromDomain = x.FromDomain,
|
||||
FromLocal = x.FromLocal,
|
||||
ToDomain = x.ToDomain,
|
||||
@@ -140,7 +139,7 @@ public class EmailBoxController(IDbContextFactory<AliasServerDbContext> dbContex
|
||||
{
|
||||
Id = x.Id,
|
||||
Subject = x.Subject,
|
||||
FromDisplay = ConversionHelper.ConvertFromToFromDisplay(x.From),
|
||||
FromDisplay = x.From,
|
||||
FromDomain = x.FromDomain,
|
||||
FromLocal = x.FromLocal,
|
||||
ToDomain = x.ToDomain,
|
||||
|
||||
@@ -45,7 +45,6 @@ public class EmailController(ILogger<VaultController> logger, IDbContextFactory<
|
||||
{
|
||||
Id = email!.Id,
|
||||
Subject = email.Subject,
|
||||
FromDisplay = ConversionHelper.ConvertFromToFromDisplay(email.From),
|
||||
FromDomain = email.FromDomain,
|
||||
FromLocal = email.FromLocal,
|
||||
ToDomain = email.ToDomain,
|
||||
@@ -71,12 +70,6 @@ public class EmailController(ILogger<VaultController> logger, IDbContextFactory<
|
||||
|
||||
returnEmail.Attachments = attachments;
|
||||
|
||||
// Enrich HTML by changing all anchor tags to open in new tab
|
||||
if (returnEmail.MessageHtml != null && !string.IsNullOrEmpty(email.MessageHtml))
|
||||
{
|
||||
returnEmail.MessageHtml = ConversionHelper.ConvertAnchorTagsToOpenInNewTab(email.MessageHtml);
|
||||
}
|
||||
|
||||
return Ok(returnEmail);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,60 +0,0 @@
|
||||
//-----------------------------------------------------------------------
|
||||
// <copyright file="ConversionHelper.cs" company="lanedirt">
|
||||
// Copyright (c) lanedirt. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE.md file in the project root for full license information.
|
||||
// </copyright>
|
||||
//-----------------------------------------------------------------------
|
||||
|
||||
namespace AliasVault.Api.Helpers;
|
||||
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
/// <summary>
|
||||
/// Class which contains various helper methods for data conversion.
|
||||
/// </summary>
|
||||
public static class ConversionHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// Extract only displayname from full "From" string. E.g. "John Doe" [johndoe@john.com] becomes "John Doe".
|
||||
/// </summary>
|
||||
/// <param name="from">The full from string.</param>
|
||||
/// <returns>Stripped displayname.</returns>
|
||||
public static string ConvertFromToFromDisplay(string from)
|
||||
{
|
||||
// Get the display name from the From field, which is everything before the first < and after the first >
|
||||
string fromDisplay = from;
|
||||
if (!from.Contains('<'))
|
||||
{
|
||||
return fromDisplay;
|
||||
}
|
||||
|
||||
// Remove everything after the last < until the last >
|
||||
fromDisplay = from.Substring(0, from.LastIndexOf('<'));
|
||||
|
||||
// Remove any double quotes
|
||||
fromDisplay = fromDisplay.Replace("\"", string.Empty);
|
||||
|
||||
// Trim any whitespace
|
||||
fromDisplay = fromDisplay.Trim();
|
||||
|
||||
return fromDisplay;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convert all anchor tags to open in a new tab.
|
||||
/// </summary>
|
||||
/// <param name="html">HTML input.</param>
|
||||
/// <returns>HTML with all anchor tags converted to open in a new tab when clicked on.</returns>
|
||||
public static string ConvertAnchorTagsToOpenInNewTab(string html)
|
||||
{
|
||||
// Match any <a tag with href attribute, regardless of the position of href or other attributes
|
||||
html = Regex.Replace(
|
||||
html,
|
||||
@"<a\s+(.*?)href=([""'])(.*?)\2(.*?)>",
|
||||
m => $"<a {m.Groups[1].Value}href={m.Groups[2].Value}{m.Groups[3].Value}{m.Groups[2].Value} {m.Groups[4].Value} target=\"_blank\">",
|
||||
RegexOptions.IgnoreCase | RegexOptions.Singleline,
|
||||
TimeSpan.FromSeconds(1));
|
||||
|
||||
return html;
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
@using AliasVault.Shared.Models.Spamok
|
||||
@using AliasVault.Shared.Utilities
|
||||
@inject JsInteropService JsInteropService
|
||||
@inject GlobalNotificationService GlobalNotificationService
|
||||
@inject IHttpClientFactory HttpClientFactory
|
||||
@@ -31,7 +32,7 @@
|
||||
</div>
|
||||
<div class="mt-4 text-gray-700 dark:text-gray-300">
|
||||
<div>
|
||||
<iframe class="w-full overscroll-y-auto" style="height:500px;" srcdoc="@(EmailBody ?? "<div>This email has no HTML content.</div>")">
|
||||
<iframe class="w-full overscroll-y-auto" style="height:500px;" srcdoc="@EmailBody">
|
||||
</iframe>
|
||||
</div>
|
||||
</div>
|
||||
@@ -176,17 +177,17 @@
|
||||
// Check if there is HTML content, if not, then set default viewtype to plain
|
||||
if (Email.MessageHtml is not null && !string.IsNullOrWhiteSpace(Email.MessageHtml))
|
||||
{
|
||||
// No HTML is available
|
||||
EmailBody = Email.MessageHtml;
|
||||
// HTML is available
|
||||
EmailBody = ConversionUtility.ConvertAnchorTagsToOpenInNewTab(Email.MessageHtml);
|
||||
}
|
||||
else if (Email.MessagePlain is not null)
|
||||
{
|
||||
// HTML is available
|
||||
// No HTML but plain text is available
|
||||
EmailBody = Email.MessagePlain;
|
||||
}
|
||||
else
|
||||
{
|
||||
// No HTML is available
|
||||
// No HTML and no plain text available
|
||||
EmailBody = "[This email has no body.]";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="HtmlAgilityPack" Version="1.11.65" />
|
||||
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.556">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
|
||||
71
src/AliasVault.Shared/Utilities/ConversionUtility.cs
Normal file
71
src/AliasVault.Shared/Utilities/ConversionUtility.cs
Normal file
@@ -0,0 +1,71 @@
|
||||
//-----------------------------------------------------------------------
|
||||
// <copyright file="ConversionUtility.cs" company="lanedirt">
|
||||
// Copyright (c) lanedirt. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE.md file in the project root for full license information.
|
||||
// </copyright>
|
||||
//-----------------------------------------------------------------------
|
||||
|
||||
namespace AliasVault.Shared.Utilities;
|
||||
|
||||
using HtmlAgilityPack;
|
||||
|
||||
/// <summary>
|
||||
/// Class which contains various helper methods for data conversion.
|
||||
/// </summary>
|
||||
public static class ConversionUtility
|
||||
{
|
||||
/// <summary>
|
||||
/// Convert all anchor tags to open in a new tab.
|
||||
/// </summary>
|
||||
/// <param name="html">HTML input.</param>
|
||||
/// <returns>HTML with all anchor tags converted to open in a new tab when clicked on.</returns>
|
||||
public static string ConvertAnchorTagsToOpenInNewTab(string html)
|
||||
{
|
||||
try
|
||||
{
|
||||
var doc = new HtmlDocument();
|
||||
doc.LoadHtml(html);
|
||||
|
||||
var anchors = doc.DocumentNode.SelectNodes("//a[@href]");
|
||||
if (anchors != null)
|
||||
{
|
||||
foreach (var anchor in anchors)
|
||||
{
|
||||
var targetAttr = anchor.Attributes["target"];
|
||||
if (targetAttr == null)
|
||||
{
|
||||
anchor.SetAttributeValue("target", "_blank");
|
||||
}
|
||||
else if (targetAttr.Value != "_blank")
|
||||
{
|
||||
targetAttr.Value = "_blank";
|
||||
}
|
||||
|
||||
// Add rel="noopener noreferrer" for security
|
||||
var relAttr = anchor.Attributes["rel"];
|
||||
if (relAttr == null)
|
||||
{
|
||||
anchor.SetAttributeValue("rel", "noopener noreferrer");
|
||||
}
|
||||
else if (!relAttr.Value.Contains("noopener") || !relAttr.Value.Contains("noreferrer"))
|
||||
{
|
||||
var relValues = new HashSet<string>(relAttr.Value.Split(' ', StringSplitOptions.RemoveEmptyEntries));
|
||||
relValues.Add("noopener");
|
||||
relValues.Add("noreferrer");
|
||||
anchor.SetAttributeValue("rel", string.Join(" ", relValues));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return doc.DocumentNode.OuterHtml;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Log the exception
|
||||
Console.WriteLine($"Error in ConvertAnchorTagsToOpenInNewTab: {ex.Message}");
|
||||
|
||||
// Return the original HTML if an error occurs
|
||||
return html;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -78,8 +78,21 @@ public class EmailDecryptionTests : ClientPlaywrightTest
|
||||
message.To.Add(new MailboxAddress("Test Recipient", email));
|
||||
const string textSubject = "Encrypted Email Subject";
|
||||
const string textBody = "This is a test email plain.";
|
||||
const string htmlBody = @"
|
||||
<html>
|
||||
<body>
|
||||
<h1>Test Email</h1>
|
||||
<p>This is a test email with HTML content.</p>
|
||||
<p>Sample anchor tag: <a href=""https://example.com"">Example Link</a></p>
|
||||
</body>
|
||||
</html>";
|
||||
message.Subject = textSubject;
|
||||
message.Body = new BodyBuilder { TextBody = textBody }.ToMessageBody();
|
||||
var bodyBuilder = new BodyBuilder
|
||||
{
|
||||
TextBody = textBody,
|
||||
HtmlBody = htmlBody,
|
||||
};
|
||||
message.Body = bodyBuilder.ToMessageBody();
|
||||
await SendMessageToSmtpServer(message);
|
||||
|
||||
// Assert that email was received by the server.
|
||||
@@ -109,6 +122,10 @@ public class EmailDecryptionTests : ClientPlaywrightTest
|
||||
await Page.Locator("text=" + textSubject).First.ClickAsync();
|
||||
await WaitForUrlAsync("emails**", "Delete");
|
||||
|
||||
// Assert that the anchor tag in the email iframe has target="_blank" attribute.
|
||||
var anchorTag = await Page.Locator("iframe").First.GetAttributeAsync("srcdoc");
|
||||
Assert.That(anchorTag, Does.Contain("target=\"_blank\""), "Anchor tag in email iframe does not have target=\"_blank\" attribute. Check email decryption logic.");
|
||||
|
||||
// Click the delete button to delete the email.
|
||||
await Page.Locator("id=delete-email").First.ClickAsync();
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ public class CodeLockoutTests : TwoFactorAuthBase
|
||||
/// </summary>
|
||||
/// <returns>Async task.</returns>
|
||||
[Test]
|
||||
public async Task TwoFactorAuthLockoutTest()
|
||||
public async Task TwoFactorAuthCodeLockoutTest()
|
||||
{
|
||||
await DisableTwoFactorIfEnabled();
|
||||
await EnableTwoFactor();
|
||||
|
||||
@@ -22,7 +22,7 @@ public class RecoveryLockoutTests : TwoFactorAuthBase
|
||||
/// </summary>
|
||||
/// <returns>Async task.</returns>
|
||||
[Test]
|
||||
public async Task TwoFactorAuthLockoutTest()
|
||||
public async Task TwoFactorAuthRecoveryLockoutTest()
|
||||
{
|
||||
await DisableTwoFactorIfEnabled();
|
||||
await EnableTwoFactor();
|
||||
|
||||
@@ -1,32 +1,20 @@
|
||||
//-----------------------------------------------------------------------
|
||||
// <copyright file="ConversionHelperTest.cs" company="lanedirt">
|
||||
// <copyright file="ConversionUtilityTest.cs" company="lanedirt">
|
||||
// Copyright (c) lanedirt. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE.md file in the project root for full license information.
|
||||
// </copyright>
|
||||
//-----------------------------------------------------------------------
|
||||
|
||||
namespace AliasVault.Tests.Helpers;
|
||||
namespace AliasVault.Tests.Utilities;
|
||||
|
||||
using AliasVault.Api.Helpers;
|
||||
using System.Text.RegularExpressions;
|
||||
using AliasVault.Shared.Utilities;
|
||||
|
||||
/// <summary>
|
||||
/// Tests for the CsvImportExport class.
|
||||
/// </summary>
|
||||
public class ConversionHelperTest
|
||||
public partial class ConversionUtilityTest
|
||||
{
|
||||
/// <summary>
|
||||
/// Tests the conversion of an email address with a display name to just the display name.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestFromConversion()
|
||||
{
|
||||
string from = "\"My full Name\" <myname@example.com>";
|
||||
string convertedFrom = ConversionHelper.ConvertFromToFromDisplay(from);
|
||||
|
||||
// Check that conversion works as expected.
|
||||
Assert.That(convertedFrom, Is.EqualTo("My full Name"));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests the conversion of a simple anchor tag to open in a new tab.
|
||||
/// </summary>
|
||||
@@ -34,7 +22,7 @@ public class ConversionHelperTest
|
||||
public void TestAnchorTabConversionSimple()
|
||||
{
|
||||
string anchorHtml = "<a href=\"https://dutchamzmasters.lt.acemlnb.com/Prod/link-tracker?redirectUrl=aHR0cHMlM0ElMkYlMkZ3d3cuZHV0Y2hhbXptYXN0ZXJzLmNvbSUyRnRoYW5rLXlvdTloN3poZ3Rp&sig=CpED3rRPX48ddoWTUZURadAYPYgPppT312jUNnvUCPo5&iat=1679512450&a=%7C%7C25799960%7C%7C&account=dutchamzmasters%2Eactivehosted%2Ecom&email=DQeVbqE%2Fy2FD5V3I2cvSxXjJCI3Tg5qfUHKGneOhzjJYZ1kM3LVZcQ%3D%3D%3AvdAW7N7fs1pZlI1ib%2BNbsMYz5m4FssAR&s=5241db963ffe25d6f4b762fc00038ee2&i=163A299A10A816\"></a>";
|
||||
string convertedAnchorTags = ConversionHelper.ConvertAnchorTagsToOpenInNewTab(anchorHtml);
|
||||
string convertedAnchorTags = ConversionUtility.ConvertAnchorTagsToOpenInNewTab(anchorHtml);
|
||||
|
||||
// Check that conversion works as expected.
|
||||
Assert.That(convertedAnchorTags, Does.Contain("target=\"_blank\""));
|
||||
@@ -47,7 +35,7 @@ public class ConversionHelperTest
|
||||
public void TestAnchorTabConversionComplex1()
|
||||
{
|
||||
string anchorHtml = "<a\nhref=\"https://dutchamzmasters.lt.acemlnb.com/Prod/link-tracker?redirectUrl=aHR0cHMlM0ElMkYlMkZ3d3cuZHV0Y2hhbXptYXN0ZXJzLmNvbSUyRnRoYW5rLXlvdTloN3poZ3Rp&sig=CpED3rRPX48ddoWTUZURadAYPYgPppT312jUNnvUCPo5&iat=1679512450&a=%7C%7C25799960%7C%7C&account=dutchamzmasters%2Eactivehosted%2Ecom&email=DQeVbqE%2Fy2FD5V3I2cvSxXjJCI3Tg5qfUHKGneOhzjJYZ1kM3LVZcQ%3D%3D%3AvdAW7N7fs1pZlI1ib%2BNbsMYz5m4FssAR&s=5241db963ffe25d6f4b762fc00038ee2&i=163A299A10A816\" data-ac-default-color=\"1\" style=\"margin: 0; outline: none; padding: 0; color: #045FB4; text-decoration: underline; font-weight: bold;\"><span style=\"color: ; font-size: inherit; font-weight: inherit; line-height: inherit; text-decoration: inherit;\">Start hier met de training >>></span></a>";
|
||||
string convertedAnchorTags = ConversionHelper.ConvertAnchorTagsToOpenInNewTab(anchorHtml);
|
||||
string convertedAnchorTags = ConversionUtility.ConvertAnchorTagsToOpenInNewTab(anchorHtml);
|
||||
|
||||
// Check that conversion works as expected.
|
||||
Assert.That(convertedAnchorTags, Does.Contain("target=\"_blank\""));
|
||||
@@ -60,7 +48,7 @@ public class ConversionHelperTest
|
||||
public void TestAnchorTabConversionComplex2()
|
||||
{
|
||||
string anchorHtml = "<div class=\"btn btn--flat btn--large\" style=\"Margin-bottom: 20px;text-align: center;\">\n <!--[if !mso]><!--><a style=\"border-radius: 4px;display: inline-block;font-size: 14px;font-weight: bold;line-height: 24px;padding: 12px 24px;text-align: center;text-decoration: none !important;transition: opacity 0.1s ease-in;color: #212529 !important;background-color: #ffdd55;font-family: Open Sans, sans-serif;\" href=\"https://eazegamesbv.cmail19.com/t/j-l-sktidll-ddhdthjhkj-j/\">Haal je beloning op</a><!--<![endif]-->\n <!--[if mso]><p style=\"line-height:0;margin:0;\"> </p><v:roundrect xmlns:v=\"urn:schemas-microsoft-com:vml\" href=\"https://eazegamesbv.cmail19.com/t/j-l-sktidll-ddhdthjhkj-j/\" style=\"width:136.5pt\" arcsize=\"9%\" fillcolor=\"#FFDD55\" stroke=\"f\"><v:textbox style=\"mso-fit-shape-to-text:t\" inset=\"0pt,8.25pt,0pt,8.25pt\"><center style=\"font-size:14px;line-height:24px;color:#212529;font-family:Open Sans,sans-serif;font-weight:bold;mso-line-height-rule:exactly;mso-text-raise:1.5px\">Haal je beloning op</center></v:textbox></v:roundrect><![endif]--></div>";
|
||||
string convertedAnchorTags = ConversionHelper.ConvertAnchorTagsToOpenInNewTab(anchorHtml);
|
||||
string convertedAnchorTags = ConversionUtility.ConvertAnchorTagsToOpenInNewTab(anchorHtml);
|
||||
|
||||
// Check that conversion works as expected.
|
||||
Assert.That(convertedAnchorTags, Does.Contain("target=\"_blank\""));
|
||||
@@ -73,9 +61,54 @@ public class ConversionHelperTest
|
||||
public void TestAnchorTabConversionComplex3()
|
||||
{
|
||||
string anchorHtml = "<td style=\"word-wrap: break-word; -webkit-hyphens: auto; -moz-hyphens: auto; hyphens: auto; vertical-align: top; font-family: Helvetica, Arial, sans-serif; font-weight: normal; margin: 0; Margin: 0; font-size: 16px; line-height: 1.3; text-align: center; color: #fefefe; background: #f17130; border-radius: 5px; border: 0 solid #f17130; width: 400px; padding: 5px; border-collapse: collapse;\"><a href=\"https://click.info.wijkopenautos.nl/f/a/chSbfTJZeP5dGZaVjOIUlw~~/AABMyAA~/RgRnzW26P0SwaHR0cHM6Ly93d3cud2lqa29wZW5hdXRvcy5ubC9pbnNwZWN0aW9uL2UwZTg0M2Y4NGEzZDRjYjc4MDYwMGU2NDEzNzc1NmEyLz9NSUQ9TkxfQ1JNXzFfM18wXzE5NjY3MF8yNDE5NTgwMTEyNDdfMSZ0bXM9MTcwOTg5Mzc4MyZ1dG1fc291cmNlPUNSTSZ1dG1fbWVkaXVtPWVtYWlsJnV0bV9jYW1wYWlnbj0zXzdXBXNwY2V1Qgpl6bro6mX9yH0mUhJidWxhYmVlckBhc2Rhc2QubmxYBAAAAAw~\" style=\"margin: 0; Margin: 0; line-height: 1.3; font-family: Helvetica, Arial, sans-serif; font-size: 16px; font-weight: bold; color: #fefefe; text-decoration: none; display: inline-block; background: #f17130; border: 0 solid #f17130; width: 400px; text-align: center; padding: 5px; border-radius: 5px;\">Ontvang nu jouw prijs <b>></b></a></td>";
|
||||
string convertedAnchorTags = ConversionHelper.ConvertAnchorTagsToOpenInNewTab(anchorHtml);
|
||||
string convertedAnchorTags = ConversionUtility.ConvertAnchorTagsToOpenInNewTab(anchorHtml);
|
||||
|
||||
// Check that conversion works as expected.
|
||||
Assert.That(convertedAnchorTags, Does.Contain("target=\"_blank\""));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests the conversion of a complex anchor tag within a table cell to open in a new tab.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestAnchorTabConversionComplex4()
|
||||
{
|
||||
string anchorHtml = "<a href=\"https://www.maxmind.com/en/account/set-password?token=FEA9D6D78B624D6BB048687F4D0A2DD9\">https://www<span>.</span>maxmind<span>.</span>com/en/account/set-password?token=FEA9D6D78B624D6BB048687F4D0A2DD9</a>";
|
||||
string convertedAnchorTags = ConversionUtility.ConvertAnchorTagsToOpenInNewTab(anchorHtml);
|
||||
|
||||
// Check that conversion works as expected.
|
||||
Assert.That(convertedAnchorTags, Does.Contain("target=\"_blank\""));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests the conversion of a complex anchor tag with existing target="_blank" attribute to
|
||||
/// not get a second attribute after conversion.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestAnchorTabConversionComplex5()
|
||||
{
|
||||
string anchorHtml = "<a href=\"test.html\" target=\"_blank\">test anchor text</a>";
|
||||
string convertedAnchorTags = ConversionUtility.ConvertAnchorTagsToOpenInNewTab(anchorHtml);
|
||||
|
||||
int targetBlankCount = TargetBlankRegex().Matches(convertedAnchorTags).Count;
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
// Check that only one target="_blank" appears.
|
||||
Assert.That(targetBlankCount, Is.EqualTo(1), "There should be exactly one target=\"_blank\" attribute.");
|
||||
|
||||
// Ensure other attributes are preserved
|
||||
Assert.That(convertedAnchorTags, Does.Contain("href=\"test.html\""), "The href attribute should be preserved.");
|
||||
|
||||
// Check that the anchor text is preserved
|
||||
Assert.That(convertedAnchorTags, Does.Contain(">test anchor text</a>"), "The anchor text should be preserved.");
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Regex to match an anchor tag with an href attribute, generated at compile time.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[GeneratedRegex("target=\"_blank\"")]
|
||||
private static partial Regex TargetBlankRegex();
|
||||
}
|
||||
Reference in New Issue
Block a user