New unit test to scan for external links/forms and if they have rel="noreferrer noopener" (#2668)

* Unit test to check for (possibly) external links

* Add rel="noreferrer noopener" to all external links so unit test passes

* Update BTCPayServer.Tests/UnitTest1.cs

Co-authored-by: Andrew Camilleri <evilkukka@gmail.com>

* Update BTCPayServer.Tests/UnitTest1.cs

Co-authored-by: Andrew Camilleri <evilkukka@gmail.com>

* Fixed bad merge from master

* PascalCasing

Co-authored-by: Andrew Camilleri <evilkukka@gmail.com>
This commit is contained in:
Wouter Samaey
2021-07-06 10:35:42 +02:00
committed by GitHub
parent 40bbc5850f
commit f1a222fbb3
69 changed files with 216 additions and 149 deletions

View File

@@ -206,12 +206,79 @@ namespace BTCPayServer.Tests
List<Task> checkLinks = new List<Task>();
foreach (var file in viewFiles)
{
checkLinks.Add(CheckLinks(regex, httpClient, file));
checkLinks.Add(CheckDeadLinks(regex, httpClient, file));
}
await Task.WhenAll(checkLinks);
}
[Fact]
[Trait("Fast", "Fast")]
public async Task CheckExternalNoReferrerLinks()
{
var views = Path.Combine(TestUtils.TryGetSolutionDirectoryInfo().FullName, "BTCPayServer", "Views");
var viewFiles = Directory.EnumerateFiles(views, "*.cshtml", SearchOption.AllDirectories).ToArray();
Assert.NotEmpty(viewFiles);
foreach (var file in viewFiles)
{
var html = await File.ReadAllTextAsync(file);
CheckHtmlNodesForReferrer(file, html, "a", "href");
CheckHtmlNodesForReferrer(file, html, "form", "action");
}
}
private void CheckHtmlNodesForReferrer(string filePath, string html, string tagName, string attribute)
{
Regex aNodeRegex = new Regex("<" + tagName + "\\s.*?>");
var matches = aNodeRegex.Matches(html).OfType<Match>();
foreach (var match in matches)
{
var node = match.Groups[0].Value;
var attributeValue = GetAttributeValue(node, attribute);
if (attributeValue != null)
{
if (attributeValue.Length == 0 || attributeValue.StartsWith("mailto:") || attributeValue.StartsWith("/") || attributeValue.StartsWith("~/") || attributeValue.StartsWith("#") || attributeValue.StartsWith("?") || attributeValue.StartsWith("javascript:") || attributeValue.StartsWith("@Url.Action("))
{
// Local link, this is fine
}
else if (attributeValue.StartsWith("http://") || attributeValue.StartsWith("https://") ||
attributeValue.StartsWith("@"))
{
// This can be an external link. Treating it as such.
var rel = GetAttributeValue(node, "rel");
// Building the file path + line number helps us to navigate to the wrong HTML quickly!
var lineNumber = html.Substring(0, html.IndexOf(node, StringComparison.InvariantCulture)).Split("\n").Length;
Assert.True(rel != null, "Template file \"" + filePath + ":" + lineNumber + "\" contains a possibly external link (" + node + ") that is missing rel=\"noreferrer noopener\"");
if (rel != null)
{
// All external links should have 'rel="noreferrer noopener"'
var relWords = rel.Split(" ");
Assert.Contains("noreferrer", relWords);
Assert.Contains("noopener", relWords);
}
}
}
}
}
private String GetAttributeValue(String nodeHtml, string attribute)
{
Regex regex = new Regex("\\s" + attribute + "=\"(.*?)\"");
var match = regex.Match(nodeHtml);
if (match.Success)
{
return match.Groups[1].Value;
}
return null;
}
[Fact]
[Trait("Integration", "Integration")]
public async Task CheckSwaggerIsConformToSchema()
@@ -286,7 +353,7 @@ namespace BTCPayServer.Tests
}
}
private static async Task CheckLinks(Regex regex, HttpClient httpClient, string file)
private static async Task CheckDeadLinks(Regex regex, HttpClient httpClient, string file)
{
List<Task> checkLinks = new List<Task>();
var text = await File.ReadAllTextAsync(file);