From ad28e0c904a0cbd9d07ddb23b841b1051904dd1e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 27 Dec 2024 04:29:58 +0000 Subject: [PATCH 1/6] Build(deps): Bump actions/setup-dotnet from 4.1.0 to 4.2.0 Bumps [actions/setup-dotnet](https://github.com/actions/setup-dotnet) from 4.1.0 to 4.2.0. - [Release notes](https://github.com/actions/setup-dotnet/releases) - [Commits](https://github.com/actions/setup-dotnet/compare/v4.1.0...v4.2.0) --- updated-dependencies: - dependency-name: actions/setup-dotnet dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .github/workflows/dotnetcore.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/dotnetcore.yml b/.github/workflows/dotnetcore.yml index dd078ac..b3fbc2d 100644 --- a/.github/workflows/dotnetcore.yml +++ b/.github/workflows/dotnetcore.yml @@ -16,7 +16,7 @@ jobs: steps: - uses: actions/checkout@v4.2.2 - name: Setup .NET Core - uses: actions/setup-dotnet@v4.1.0 + uses: actions/setup-dotnet@v4.2.0 with: dotnet-version: 3.1.101 - name: Install dependencies From 81693ce029e8048393c414b9e63fdc2f20616855 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20H=C3=B8st=20Normark?= Date: Thu, 23 Jan 2025 11:56:29 +0100 Subject: [PATCH 2/6] Update target frameworks of test and benchmark projects (#420) * Build(deps): Update target framework of test project to net9.0 and bump package versions * Build(deps): Update target framework of benchmark project to net9.0 and upgrade BenchmarkDotNet to version 0.14.0 --- PreMailer.Net/Benchmarks/Benchmarks.csproj | 4 ++-- .../PreMailer.Net.Tests/PreMailer.Net.Tests.csproj | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/PreMailer.Net/Benchmarks/Benchmarks.csproj b/PreMailer.Net/Benchmarks/Benchmarks.csproj index 4731aa3..22100bf 100644 --- a/PreMailer.Net/Benchmarks/Benchmarks.csproj +++ b/PreMailer.Net/Benchmarks/Benchmarks.csproj @@ -2,12 +2,12 @@ Exe - net7.0 + net9.0 enable - + diff --git a/PreMailer.Net/PreMailer.Net.Tests/PreMailer.Net.Tests.csproj b/PreMailer.Net/PreMailer.Net.Tests/PreMailer.Net.Tests.csproj index 8f975c7..f6ea2bc 100644 --- a/PreMailer.Net/PreMailer.Net.Tests/PreMailer.Net.Tests.csproj +++ b/PreMailer.Net/PreMailer.Net.Tests/PreMailer.Net.Tests.csproj @@ -1,17 +1,17 @@ - net7.0 + net9.0 false - - - - - + + + + + From adb1b22578f013234396e99b1ef11b630554d410 Mon Sep 17 00:00:00 2001 From: whorchner Date: Wed, 29 Jan 2025 15:57:13 +0100 Subject: [PATCH 3/6] LinkTagCssSource now also downloads content from @import declarations. (#419) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * LinkTagCssSource now also downloads content from @import declarations. PreMailer: - fixed obsolete DescendentsAndSelf to DescendantsAndSelf * LinkTagCssSource now also downloads content from @import declarations. Extracted the import logic to it's own ImportRuleCssSource class. PreMailer: - fixed obsolete DescendentsAndSelf to DescendantsAndSelf * Revert project file changes * Add an ImportRuleCssSourceTest class * Add an ImportRuleCssSourceTest class * Changed ICssSource and classes based on it to return a IEnumerable. Changed ImportRuleCssSource (and LinkTagCssSource) to return a list of css blocks. * Simplify selection of multiple CSS sources * Added a stylesheet for testing purposes. Can be referenced with a url to github. * Last commit with TestStylesheet.css was not necesarry. Removed it. * Refactor ImportRuleCssSource and related tests for improved clarity and functionality * I first have to commit some changes before I can pull * Because imported stylesheets themselfs can reference the same stylesheet in their import list, checking if the url already exists in the _importList is necessary. * Removed the preparation for another PR. * Add recursive import handling and caching in ImportRuleCssSource tests - Implemented tests for loading CSS imports recursively up to two levels. - Added caching mechanism to prevent multiple downloads of the same stylesheet. - Ensured import order is preserved when processing multiple imports. - Handled circular imports to avoid infinite loops during CSS retrieval. - Enhanced test coverage for ImportRuleCssSource functionality. * Avoid use of deprecated xUnit assert functions * Remove debug Console.WriteLine statements --------- Co-authored-by: W Hörchner Co-authored-by: Martin Høst Normark --- .../CssStyleEquivalenceTests.cs | 4 +- .../ImportRuleCssSourceTests.cs | 217 ++++++++++++++++++ .../PreMailer.Net.Tests/PreMailerTests.cs | 2 +- PreMailer.Net/PreMailer.Net/PreMailer.cs | 6 +- .../Sources/DocumentStyleTagCssSource.cs | 7 +- .../PreMailer.Net/Sources/ICssSource.cs | 6 +- .../Sources/ImportRuleCssSource.cs | 97 ++++++++ .../PreMailer.Net/Sources/LinkTagCssSource.cs | 55 +++-- .../PreMailer.Net/Sources/StringCssSource.cs | 6 +- 9 files changed, 370 insertions(+), 30 deletions(-) create mode 100644 PreMailer.Net/PreMailer.Net.Tests/ImportRuleCssSourceTests.cs create mode 100644 PreMailer.Net/PreMailer.Net/Sources/ImportRuleCssSource.cs diff --git a/PreMailer.Net/PreMailer.Net.Tests/CssStyleEquivalenceTests.cs b/PreMailer.Net/PreMailer.Net.Tests/CssStyleEquivalenceTests.cs index b552377..9665d98 100644 --- a/PreMailer.Net/PreMailer.Net.Tests/CssStyleEquivalenceTests.cs +++ b/PreMailer.Net/PreMailer.Net.Tests/CssStyleEquivalenceTests.cs @@ -17,7 +17,7 @@ public void FindEquivalentStyles() var result = CssStyleEquivalence.FindEquivalent(nodewithoutselector, clazz); - Assert.Equal(1, result.Count); + Assert.Single(result); } [Fact] @@ -32,7 +32,7 @@ public void FindEquivalentStylesNoMatchingStyles() var result = CssStyleEquivalence.FindEquivalent(nodewithoutselector, clazz); - Assert.Equal(0, result.Count); + Assert.Empty(result); } } } \ No newline at end of file diff --git a/PreMailer.Net/PreMailer.Net.Tests/ImportRuleCssSourceTests.cs b/PreMailer.Net/PreMailer.Net.Tests/ImportRuleCssSourceTests.cs new file mode 100644 index 0000000..0b9d185 --- /dev/null +++ b/PreMailer.Net/PreMailer.Net.Tests/ImportRuleCssSourceTests.cs @@ -0,0 +1,217 @@ +using Xunit; +using Moq; +using PreMailer.Net.Downloaders; +using PreMailer.Net.Sources; +using System; +using System.Text; +using System.Collections.Generic; +using System.Linq; + +namespace PreMailer.Net.Tests +{ + public class ImportRuleCssSourceTests + { + private readonly Mock _webDownloader = new Mock(); + + public ImportRuleCssSourceTests() + { + WebDownloader.SharedDownloader = _webDownloader.Object; + } + + [Fact] + public void ItShould_DownloadAllImportedUrls_WhenCssContainsImportRules() + { + var baseUri = new Uri("https://a.com"); + var urls = new List() { "variables.css?v234", "/fonts.css", "https://fonts.google.com/css/test-font" }; + + var css = CreateCss(urls); + var sut = new ImportRuleCssSource(); + + sut.GetCss(baseUri, css); + + foreach (var url in urls) + { + _webDownloader.Verify(w => w.DownloadString(It.Is(u => u.Equals(new Uri(baseUri, url))))); + } + } + + [Fact] + public void ItShould_NotDownloadUrls_WhenLevelIsGreaterThanTwo() + { + var baseUri = new Uri("https://a.com"); + var urls = new List() { "variables.css?v234", "/fonts.css", "https://fonts.google.com/css/test-font" }; + + var css = CreateCss(urls); + var sut = new ImportRuleCssSource(); + + sut.GetCss(baseUri, css, 2); + + _webDownloader.Verify(w => w.DownloadString(It.IsAny()), Times.Never()); + } + + [Fact] + public void ItShould_NotDownloadUrls_WhenCssIsEmpty() + { + var baseUri = new Uri("https://a.com"); + var urls = new List() { "variables.css?v234", "/fonts.css", "https://fonts.google.com/css/test-font" }; + + var css = CreateCss(urls); + var sut = new ImportRuleCssSource(); + + sut.GetCss(baseUri, string.Empty); + + _webDownloader.Verify(w => w.DownloadString(It.IsAny()), Times.Never()); + } + + [Fact] + public void ItShould_NotDownloadUrls_WhenCssIsNull() + { + var baseUri = new Uri("https://a.com"); + var urls = new List() { "variables.css?v234", "/fonts.css", "https://fonts.google.com/css/test-font" }; + + var css = CreateCss(urls); + var sut = new ImportRuleCssSource(); + + sut.GetCss(baseUri, null); + + _webDownloader.Verify(w => w.DownloadString(It.IsAny()), Times.Never()); + } + + [Fact] + public void ItShould_NotDownloadUrls_WhenCssDoesNotContainImportRules() + { + var baseUri = new Uri("https://a.com"); + var urls = new List() { "variables.css?v234", "/fonts.css", "https://fonts.google.com/css/test-font" }; + + var css = string.Join(Environment.NewLine, urls); + var sut = new ImportRuleCssSource(); + + sut.GetCss(baseUri, css); + + _webDownloader.Verify(w => w.DownloadString(It.IsAny()), Times.Never()); + } + + [Fact] + public void ItShould_LoadImportsRecursively_UntilLevelTwo() + { + // Arrange + var baseUri = new Uri("https://a.com"); + var level0Css = "@import \"level1.css\";"; + var level1Css = "@import \"level2.css\";"; + var level2Css = "@import \"level3.css\";"; + + _webDownloader + .Setup(w => w.DownloadString(It.Is(u => u.ToString() == "https://a.com/level1.css"))) + .Returns(level1Css); + _webDownloader + .Setup(w => w.DownloadString(It.Is(u => u.ToString() == "https://a.com/level2.css"))) + .Returns(level2Css); + + var sut = new ImportRuleCssSource(); + + // Act + var result = sut.GetCss(baseUri, level0Css).ToList(); + + // Assert + _webDownloader.Verify(w => w.DownloadString(It.Is(u => u.ToString() == "https://a.com/level1.css")), Times.Once); + _webDownloader.Verify(w => w.DownloadString(It.Is(u => u.ToString() == "https://a.com/level2.css")), Times.Once); + _webDownloader.Verify(w => w.DownloadString(It.Is(u => u.ToString() == "https://a.com/level3.css")), Times.Never); + Assert.Equal(2, result.Count); // Should only contain level1.css and level2.css contents + } + + [Fact] + public void ItShould_CacheDownloadedImports_AndNotDownloadTwice() + { + // Arrange + var baseUri = new Uri("https://a.com"); + var css = "@import \"shared.css\"; @import \"also-shared.css\";"; + var secondCss = "@import \"shared.css\";"; // References same file + + _webDownloader + .Setup(w => w.DownloadString(It.Is(u => u.ToString() == "https://a.com/shared.css"))) + .Returns("h1 { color: red; }"); + _webDownloader + .Setup(w => w.DownloadString(It.Is(u => u.ToString() == "https://a.com/also-shared.css"))) + .Returns("h2 { color: blue; }"); + + var sut = new ImportRuleCssSource(); + + // Act + var firstResult = sut.GetCss(baseUri, css); + var secondResult = sut.GetCss(baseUri, secondCss); + + // Assert + _webDownloader.Verify(w => w.DownloadString(It.Is(u => u.ToString() == "https://a.com/shared.css")), Times.Once); + } + + [Fact] + public void ItShould_PreserveImportOrder_WhenProcessingImports() + { + // Arrange + var baseUri = new Uri("https://a.com"); + var css = "@import \"first.css\"; @import \"second.css\"; @import \"third.css\";"; + var firstCss = "first { order: 1; }"; + var secondCss = "second { order: 2; }"; + var thirdCss = "third { order: 3; }"; + + _webDownloader + .Setup(w => w.DownloadString(It.Is(u => u.ToString() == "https://a.com/first.css"))) + .Returns(firstCss); + _webDownloader + .Setup(w => w.DownloadString(It.Is(u => u.ToString() == "https://a.com/second.css"))) + .Returns(secondCss); + _webDownloader + .Setup(w => w.DownloadString(It.Is(u => u.ToString() == "https://a.com/third.css"))) + .Returns(thirdCss); + + var sut = new ImportRuleCssSource(); + + // Act + var result = sut.GetCss(baseUri, css).ToList(); + + // Assert + Assert.Equal(3, result.Count); + Assert.Equal(firstCss, result[0]); + Assert.Equal(secondCss, result[1]); + Assert.Equal(thirdCss, result[2]); + } + + [Fact] + public void ItShould_HandleCircularImports_WithoutInfiniteLoop() + { + // Arrange + var baseUri = new Uri("https://a.com"); + var css = "@import \"circular1.css\";"; + var circular1Css = "@import \"circular2.css\";"; + var circular2Css = "@import \"circular1.css\";"; // Creates a circle + + _webDownloader + .Setup(w => w.DownloadString(It.Is(u => u.ToString() == "https://a.com/circular1.css"))) + .Returns(circular1Css); + _webDownloader + .Setup(w => w.DownloadString(It.Is(u => u.ToString() == "https://a.com/circular2.css"))) + .Returns(circular2Css); + + var sut = new ImportRuleCssSource(); + + // Act + var result = sut.GetCss(baseUri, css).ToList(); + + // Assert + Assert.Equal(2, result.Count); // Should contain both files exactly once + _webDownloader.Verify(w => w.DownloadString(It.Is(u => u.ToString() == "https://a.com/circular1.css")), Times.Once); + _webDownloader.Verify(w => w.DownloadString(It.Is(u => u.ToString() == "https://a.com/circular2.css")), Times.Once); + } + + private string CreateCss(IEnumerable imports) + { + var builder = new StringBuilder(); + foreach (var import in imports) + { + builder.AppendLine($"@import \"{import}\";"); + } + + return builder.ToString(); + } + } +} diff --git a/PreMailer.Net/PreMailer.Net.Tests/PreMailerTests.cs b/PreMailer.Net/PreMailer.Net.Tests/PreMailerTests.cs index c34e915..4db0e0e 100644 --- a/PreMailer.Net/PreMailer.Net.Tests/PreMailerTests.cs +++ b/PreMailer.Net/PreMailer.Net.Tests/PreMailerTests.cs @@ -104,7 +104,7 @@ public void MoveCssInline_CrazyCssSelector_DoesNotThrowError() } catch (Exception ex) { - Assert.True(false, ex.Message); + Assert.Fail(ex.Message); } } diff --git a/PreMailer.Net/PreMailer.Net/PreMailer.cs b/PreMailer.Net/PreMailer.Net/PreMailer.cs index 5621249..34a402c 100644 --- a/PreMailer.Net/PreMailer.Net/PreMailer.cs +++ b/PreMailer.Net/PreMailer.Net/PreMailer.cs @@ -247,7 +247,7 @@ private bool DomainMatch(string domain, string url) /// private IEnumerable GetCssBlocks(IEnumerable cssSources) { - return cssSources.Select(styleSource => styleSource.GetCss()).ToList(); + return cssSources.SelectMany(styleSource => styleSource.GetCss()).ToList(); } private void RemoveCssComments(IEnumerable cssSourceNodes) @@ -405,7 +405,7 @@ private Dictionary> FindElementsWithStyles( Selector = selectorParser.ParseSelector(x.Value.Name) }).Where(x => x.Selector != null).ToList(); - foreach (var el in _document.DescendentsAndSelf()) + foreach (var el in _document.DescendantsAndSelf()) { foreach (var style in styles) { @@ -516,7 +516,7 @@ private int GetSelectorSpecificity(Dictionary cache, string selecto private void RemoveHtmlComments() { - var comments = _document.Descendents().ToList(); + var comments = _document.Descendants().ToList(); foreach (var comment in comments) { diff --git a/PreMailer.Net/PreMailer.Net/Sources/DocumentStyleTagCssSource.cs b/PreMailer.Net/PreMailer.Net/Sources/DocumentStyleTagCssSource.cs index 4b263b8..6b3f986 100644 --- a/PreMailer.Net/PreMailer.Net/Sources/DocumentStyleTagCssSource.cs +++ b/PreMailer.Net/PreMailer.Net/Sources/DocumentStyleTagCssSource.cs @@ -1,4 +1,5 @@ -using AngleSharp.Dom; +using System.Collections.Generic; +using AngleSharp.Dom; using PreMailer.Net.Extensions; namespace PreMailer.Net.Sources @@ -12,9 +13,9 @@ public DocumentStyleTagCssSource(IElement node) _node = node; } - public string GetCss() + public IEnumerable GetCss() { - return _node.GetFirstTextNodeData() ?? ""; + return [_node.GetFirstTextNodeData() ?? ""]; } } } \ No newline at end of file diff --git a/PreMailer.Net/PreMailer.Net/Sources/ICssSource.cs b/PreMailer.Net/PreMailer.Net/Sources/ICssSource.cs index db67824..43cc1d1 100644 --- a/PreMailer.Net/PreMailer.Net/Sources/ICssSource.cs +++ b/PreMailer.Net/PreMailer.Net/Sources/ICssSource.cs @@ -1,10 +1,12 @@ -namespace PreMailer.Net.Sources +using System.Collections.Generic; + +namespace PreMailer.Net.Sources { /// /// Arbitrary source of CSS code/definitions. /// public interface ICssSource { - string GetCss(); + IEnumerable GetCss(); } } \ No newline at end of file diff --git a/PreMailer.Net/PreMailer.Net/Sources/ImportRuleCssSource.cs b/PreMailer.Net/PreMailer.Net/Sources/ImportRuleCssSource.cs new file mode 100644 index 0000000..0217f61 --- /dev/null +++ b/PreMailer.Net/PreMailer.Net/Sources/ImportRuleCssSource.cs @@ -0,0 +1,97 @@ +using System; +using System.Collections.Generic; +using System.Net; +using System.Text.RegularExpressions; +using PreMailer.Net.Downloaders; + +namespace PreMailer.Net.Sources +{ + /// + /// This class is used by the LinkTagCssSource class for downloading/fetching import rules. + /// + public class ImportRuleCssSource + { + private Dictionary _importList = new Dictionary(); + private static Regex _importRegex = new Regex("@import.*?[\"'](?[^\"']+)[\"'].*?;", RegexOptions.Multiline | RegexOptions.IgnoreCase); + + public IEnumerable GetCss(Uri linkedStylesheetUrl, string contents, int level = 0) + { + if (level >= 2 || string.IsNullOrEmpty(contents)) + { + return _importList.Values; + } + + var baseUri = GetBaseUri(linkedStylesheetUrl); + var matches = GetMatches(contents); + + foreach (Match match in matches) + { + var href = match.Groups["href"].Value; + Uri url = default; + + if (Uri.IsWellFormedUriString(href, UriKind.Relative)) + { + url = new Uri(baseUri, href); + } + else + { + url = new Uri(href); + } + + if (!_importList.ContainsKey(url)) + { + var content = DownloadContents(url); + + _importList.Add(url, content); + + GetCss(url, content, level + 1); + } + } + + return _importList.Values; + } + + private string DownloadContents(Uri downloadUri) + { + string contents; + + try + { + contents = WebDownloader.SharedDownloader.DownloadString(downloadUri); + } + catch (WebException ex) + { + throw new WebException($"PreMailer.Net is unable to download the requested URL: {downloadUri}", ex); + } + + return contents; + } + + private static bool IsSupported(string scheme) => scheme == "http" || scheme == "https"; + + private static MatchCollection GetMatches(string contents) + { + if (string.IsNullOrEmpty(contents)) + { + return default; + } + + return _importRegex.Matches(contents); + } + + private static Uri GetBaseUri(Uri downloadUri) + { + var baseUrl = new UriBuilder(downloadUri) + { + Port = -1 /* Excludes the port number */, + Query = string.Empty + }; + + // Strip of the css file segment + var path = baseUrl.Path; + baseUrl.Path = path.Substring(0, path.LastIndexOf('/') + 1); + + return baseUrl.Uri; + } + } +} diff --git a/PreMailer.Net/PreMailer.Net/Sources/LinkTagCssSource.cs b/PreMailer.Net/PreMailer.Net/Sources/LinkTagCssSource.cs index 308fe27..669bdc8 100644 --- a/PreMailer.Net/PreMailer.Net/Sources/LinkTagCssSource.cs +++ b/PreMailer.Net/PreMailer.Net/Sources/LinkTagCssSource.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Linq; using System.Net; using AngleSharp.Dom; @@ -9,10 +10,16 @@ namespace PreMailer.Net.Sources public class LinkTagCssSource : ICssSource { private readonly Uri _downloadUri; - private string _cssContents; + private List _cssContents; + private ImportRuleCssSource _importRuleCssSource; + public LinkTagCssSource(IElement node, Uri baseUri) : this(node, baseUri, new ImportRuleCssSource()) + { + } - public LinkTagCssSource(IElement node, Uri baseUri) + public LinkTagCssSource(IElement node, Uri baseUri, ImportRuleCssSource importRuleCssSource) { + _importRuleCssSource = importRuleCssSource; + // There must be an href var href = node.Attributes.First(a => a.Name.Equals("href", StringComparison.OrdinalIgnoreCase)).Value; @@ -27,24 +34,40 @@ public LinkTagCssSource(IElement node, Uri baseUri) } } - public string GetCss() + public IEnumerable GetCss() { - Console.WriteLine($"GetCss scheme: {_downloadUri.Scheme}"); - if (IsSupported(_downloadUri.Scheme)) { - try - { - Console.WriteLine($"Will download from '{_downloadUri}' using {WebDownloader.SharedDownloader.GetType()}"); - - return _cssContents ?? (_cssContents = WebDownloader.SharedDownloader.DownloadString(_downloadUri)); - } - catch (WebException) - { - throw new WebException($"PreMailer.Net is unable to fetch the requested URL: {_downloadUri}"); - } + return _cssContents ?? DownloadContents(); + } + return default; + } + + private List DownloadContents() + { + string content; + _cssContents ??= new(); + + try + { + content = WebDownloader.SharedDownloader.DownloadString(_downloadUri); + } + catch (WebException ex) + { + throw new WebException($"PreMailer.Net is unable to download the requested URL: {_downloadUri}", ex); } - return string.Empty; + + // Fetch possible import rules + var imports = _importRuleCssSource.GetCss(_downloadUri, content); + + if (imports != null) + { + _cssContents.AddRange(imports); + } + + _cssContents.Add(content); + + return _cssContents; } private static bool IsSupported(string scheme) diff --git a/PreMailer.Net/PreMailer.Net/Sources/StringCssSource.cs b/PreMailer.Net/PreMailer.Net/Sources/StringCssSource.cs index ae5df3d..39fbf16 100644 --- a/PreMailer.Net/PreMailer.Net/Sources/StringCssSource.cs +++ b/PreMailer.Net/PreMailer.Net/Sources/StringCssSource.cs @@ -1,4 +1,4 @@ -// No usings needed +using System.Collections.Generic; namespace PreMailer.Net.Sources { @@ -11,9 +11,9 @@ public StringCssSource(string css) this._css = css; } - public string GetCss() + public IEnumerable GetCss() { - return _css; + return [_css]; } } } \ No newline at end of file From 004c7b752a1eb3574da4a1e569823bc38b9c484d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 30 Jan 2025 04:03:55 +0000 Subject: [PATCH 4/6] Build(deps): Bump actions/setup-dotnet from 4.2.0 to 4.3.0 Bumps [actions/setup-dotnet](https://github.com/actions/setup-dotnet) from 4.2.0 to 4.3.0. - [Release notes](https://github.com/actions/setup-dotnet/releases) - [Commits](https://github.com/actions/setup-dotnet/compare/v4.2.0...v4.3.0) --- updated-dependencies: - dependency-name: actions/setup-dotnet dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .github/workflows/dotnetcore.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/dotnetcore.yml b/.github/workflows/dotnetcore.yml index b3fbc2d..86d437c 100644 --- a/.github/workflows/dotnetcore.yml +++ b/.github/workflows/dotnetcore.yml @@ -16,7 +16,7 @@ jobs: steps: - uses: actions/checkout@v4.2.2 - name: Setup .NET Core - uses: actions/setup-dotnet@v4.2.0 + uses: actions/setup-dotnet@v4.3.0 with: dotnet-version: 3.1.101 - name: Install dependencies From 589db0505f19e93ba9b34030a216061b444e0cae Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 17 Mar 2025 04:38:56 +0000 Subject: [PATCH 5/6] Build(deps): Bump actions/setup-dotnet from 4.3.0 to 4.3.1 Bumps [actions/setup-dotnet](https://github.com/actions/setup-dotnet) from 4.3.0 to 4.3.1. - [Release notes](https://github.com/actions/setup-dotnet/releases) - [Commits](https://github.com/actions/setup-dotnet/compare/v4.3.0...v4.3.1) --- updated-dependencies: - dependency-name: actions/setup-dotnet dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/workflows/dotnetcore.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/dotnetcore.yml b/.github/workflows/dotnetcore.yml index 86d437c..ff19362 100644 --- a/.github/workflows/dotnetcore.yml +++ b/.github/workflows/dotnetcore.yml @@ -16,7 +16,7 @@ jobs: steps: - uses: actions/checkout@v4.2.2 - name: Setup .NET Core - uses: actions/setup-dotnet@v4.3.0 + uses: actions/setup-dotnet@v4.3.1 with: dotnet-version: 3.1.101 - name: Install dependencies From b3bda0925c7667b8181e3330b10d047659758c2f Mon Sep 17 00:00:00 2001 From: Adam Grande Date: Thu, 7 Nov 2024 11:25:07 +0800 Subject: [PATCH 6/6] Ensure external sources for styles only process CSS responses when inlining stylesheets --- PreMailer.Net/Benchmarks/Program.cs | 19 ++++++++++++--- .../Downloaders/WebDownloader.cs | 11 ++++++++- .../Extensions/WebResponseExtensions.cs | 24 +++++++++++++++++++ 3 files changed, 50 insertions(+), 4 deletions(-) create mode 100644 PreMailer.Net/PreMailer.Net/Extensions/WebResponseExtensions.cs diff --git a/PreMailer.Net/Benchmarks/Program.cs b/PreMailer.Net/Benchmarks/Program.cs index f04d91e..025fffd 100644 --- a/PreMailer.Net/Benchmarks/Program.cs +++ b/PreMailer.Net/Benchmarks/Program.cs @@ -1,17 +1,30 @@ using AngleSharp; using AngleSharp.Html.Parser; using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Configs; +using BenchmarkDotNet.Jobs; using BenchmarkDotNet.Running; +using BenchmarkDotNet.Toolchains.InProcess.NoEmit; public static class Program { public static void Main() { - BenchmarkRunner.Run(); + // Some local environments may run into issues with Windows Defender or + // SentinelOne (and others) when running a benchmark. This ensures we + // keep our toolchain within our process and stops the above apps from blocking + // our benchmark process, but can slow the execution time. + var avSafeConfig = DefaultConfig.Instance + .AddJob( + Job.ShortRun + .WithToolchain(InProcessNoEmitToolchain.Instance) + .WithIterationCount(100) + ); + + BenchmarkRunner.Run(avSafeConfig); } } -[SimpleJob(invocationCount: 100, iterationCount: 100)] [MemoryDiagnoser] public class Realistic { @@ -47,7 +60,7 @@ public void MoveCssInline_AllFlags() PreMailer Benchmark - + diff --git a/PreMailer.Net/PreMailer.Net/Downloaders/WebDownloader.cs b/PreMailer.Net/PreMailer.Net/Downloaders/WebDownloader.cs index 281e7f3..d04de4d 100644 --- a/PreMailer.Net/PreMailer.Net/Downloaders/WebDownloader.cs +++ b/PreMailer.Net/PreMailer.Net/Downloaders/WebDownloader.cs @@ -1,3 +1,4 @@ +using PreMailer.Net.Extensions; using System; using System.IO; using System.Net; @@ -8,7 +9,7 @@ namespace PreMailer.Net.Downloaders public class WebDownloader : IWebDownloader { private static IWebDownloader _sharedDownloader; - + private const string CssMimeType = "text/css"; public static IWebDownloader SharedDownloader { get @@ -29,8 +30,16 @@ public static IWebDownloader SharedDownloader public string DownloadString(Uri uri) { var request = WebRequest.Create(uri); + request.Headers.Add(HttpRequestHeader.Accept, CssMimeType); + using (var response = request.GetResponse()) { + // We only support this operation for CSS file/content types coming back + // from the response. If we get something different, throw with the unsupported + // content type in the message + if(response.ParseContentType() != CssMimeType) + throw new NotSupportedException($"The Uri type is giving a response in unsupported content type '{response.ContentType}'."); + switch (response) { case HttpWebResponse httpWebResponse: diff --git a/PreMailer.Net/PreMailer.Net/Extensions/WebResponseExtensions.cs b/PreMailer.Net/PreMailer.Net/Extensions/WebResponseExtensions.cs new file mode 100644 index 0000000..5508a35 --- /dev/null +++ b/PreMailer.Net/PreMailer.Net/Extensions/WebResponseExtensions.cs @@ -0,0 +1,24 @@ +using System; +using System.Net; + +namespace PreMailer.Net.Extensions +{ + public static class WebResponseExtensions + { + public static string ParseContentType(this WebResponse response) + { + if(response == null) + throw new NullReferenceException("Malformed response detected when parsing WebResponse Content-Type"); + + if(string.IsNullOrEmpty(response.ContentType)) + throw new NullReferenceException("Malformed Content-Type response detected when parsing WebResponse"); + + var results = response.ContentType.Split(';'); + + if(results.Length == 0) + throw new FormatException("Malformed Content-Type response detected when parsing WebResponse"); + + return results[0]; + } + } +}