From 1ef5290576c43489b88783f50cfe8ef24ce5d24c Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 9 Jul 2021 14:50:37 -0600 Subject: [PATCH] Updates runtime hash calc to be based on the assemblies returned from the assembly provider. --- .../DefaultUmbracoAssemblyProvider.cs | 11 ++++- src/Umbraco.Core/Composing/RuntimeHash.cs | 46 ++++++++++--------- src/Umbraco.Core/Composing/TypeFinder.cs | 4 +- src/Umbraco.Core/HashGenerator.cs | 8 ++-- src/Umbraco.Core/IO/FileSystemExtensions.cs | 23 +++++++++- .../UmbracoTestServerTestBase.cs | 2 - .../Testing/UmbracoIntegrationTest.cs | 1 - .../UmbracoBuilderExtensions.cs | 2 +- .../Extensions/ServiceCollectionExtensions.cs | 20 +++++--- 9 files changed, 76 insertions(+), 41 deletions(-) diff --git a/src/Umbraco.Core/Composing/DefaultUmbracoAssemblyProvider.cs b/src/Umbraco.Core/Composing/DefaultUmbracoAssemblyProvider.cs index 60aa666034..52967a58e7 100644 --- a/src/Umbraco.Core/Composing/DefaultUmbracoAssemblyProvider.cs +++ b/src/Umbraco.Core/Composing/DefaultUmbracoAssemblyProvider.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Reflection; using Microsoft.Extensions.Logging; @@ -16,6 +17,7 @@ namespace Umbraco.Cms.Core.Composing { private readonly Assembly _entryPointAssembly; private readonly ILoggerFactory _loggerFactory; + private List _discovered; public DefaultUmbracoAssemblyProvider(Assembly entryPointAssembly, ILoggerFactory loggerFactory) { @@ -33,8 +35,15 @@ namespace Umbraco.Cms.Core.Composing { get { + if (_discovered != null) + { + return _discovered; + } + var finder = new FindAssembliesWithReferencesTo(new[] { _entryPointAssembly }, Constants.Composing.UmbracoCoreAssemblyNames, true, _loggerFactory); - return finder.Find(); + _discovered = finder.Find().ToList(); + + return _discovered; } } } diff --git a/src/Umbraco.Core/Composing/RuntimeHash.cs b/src/Umbraco.Core/Composing/RuntimeHash.cs index 4eb70cea1f..7ca35855de 100644 --- a/src/Umbraco.Core/Composing/RuntimeHash.cs +++ b/src/Umbraco.Core/Composing/RuntimeHash.cs @@ -1,7 +1,9 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using System.Security.Cryptography; using Umbraco.Cms.Core.Logging; +using Umbraco.Extensions; namespace Umbraco.Cms.Core.Composing { @@ -12,6 +14,7 @@ namespace Umbraco.Cms.Core.Composing { private readonly IProfilingLogger _logger; private readonly RuntimeHashPaths _paths; + private string _calculated; public RuntimeHash(IProfilingLogger logger, RuntimeHashPaths paths) { @@ -22,13 +25,18 @@ namespace Umbraco.Cms.Core.Composing public string GetHashValue() { - var allPaths = _paths.GetFolders() - .Select(x => ((FileSystemInfo) x, false)) - .Concat(_paths.GetFiles().Select(x => ((FileSystemInfo) x.Key, x.Value))); + if (_calculated != null) + { + return _calculated; + } - var hash = GetFileHash(allPaths); + IEnumerable<(FileSystemInfo, bool)> allPaths = _paths.GetFolders() + .Select(x => ((FileSystemInfo)x, false)) + .Concat(_paths.GetFiles().Select(x => ((FileSystemInfo)x.Key, x.Value))); - return hash; + _calculated = GetFileHash(allPaths); + + return _calculated; } /// @@ -48,7 +56,7 @@ namespace Umbraco.Cms.Core.Composing using var generator = new HashGenerator(); - foreach (var (fileOrFolder, scanFileContent) in filesAndFolders) + foreach ((FileSystemInfo fileOrFolder, bool scanFileContent) in filesAndFolders) { if (scanFileContent) { @@ -56,9 +64,16 @@ namespace Umbraco.Cms.Core.Composing // normalize the content for cr/lf and case-sensitivity if (uniqContent.Add(fileOrFolder.FullName)) { - if (File.Exists(fileOrFolder.FullName) == false) continue; - var content = RemoveCrLf(File.ReadAllText(fileOrFolder.FullName)); - generator.AddCaseInsensitiveString(content); + if (File.Exists(fileOrFolder.FullName) == false) + { + continue; + } + + using (FileStream fileStream = File.OpenRead(fileOrFolder.FullName)) + { + var hash = fileStream.GetStreamHash(); + generator.AddCaseInsensitiveString(hash); + } } } else @@ -74,18 +89,5 @@ namespace Umbraco.Cms.Core.Composing } } - // fast! (yes, according to benchmarks) - private static string RemoveCrLf(string s) - { - var buffer = new char[s.Length]; - var count = 0; - // ReSharper disable once ForCanBeConvertedToForeach - no! - for (var i = 0; i < s.Length; i++) - { - if (s[i] != '\r' && s[i] != '\n') - buffer[count++] = s[i]; - } - return new string(buffer, 0, count); - } } } diff --git a/src/Umbraco.Core/Composing/TypeFinder.cs b/src/Umbraco.Core/Composing/TypeFinder.cs index 1979f33d10..97b8e3847c 100644 --- a/src/Umbraco.Core/Composing/TypeFinder.cs +++ b/src/Umbraco.Core/Composing/TypeFinder.cs @@ -21,7 +21,7 @@ namespace Umbraco.Cms.Core.Composing private volatile HashSet _localFilteredAssemblyCache; private readonly object _localFilteredAssemblyCacheLocker = new object(); private readonly List _notifiedLoadExceptionAssemblies = new List(); - private static readonly ConcurrentDictionary TypeNamesCache = new ConcurrentDictionary(); + private static readonly ConcurrentDictionary s_typeNamesCache = new ConcurrentDictionary(); private readonly ITypeFinderConfig _typeFinderConfig; // used for benchmark tests @@ -255,7 +255,7 @@ namespace Umbraco.Cms.Core.Composing } // It didn't parse, so try loading from each already loaded assembly and cache it - return TypeNamesCache.GetOrAdd(name, s => + return s_typeNamesCache.GetOrAdd(name, s => AppDomain.CurrentDomain.GetAssemblies() .Select(x => x.GetType(s)) .FirstOrDefault(x => x != null)); diff --git a/src/Umbraco.Core/HashGenerator.cs b/src/Umbraco.Core/HashGenerator.cs index 255cf381af..944e0bdf49 100644 --- a/src/Umbraco.Core/HashGenerator.cs +++ b/src/Umbraco.Core/HashGenerator.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.IO; using System.Security.Cryptography; using System.Text; @@ -73,14 +73,12 @@ namespace Umbraco.Cms.Core AddDateTime(f.LastWriteTimeUtc); //check if it is a file or folder - var fileInfo = f as FileInfo; - if (fileInfo != null) + if (f is FileInfo fileInfo) { AddLong(fileInfo.Length); } - var dirInfo = f as DirectoryInfo; - if (dirInfo != null) + if (f is DirectoryInfo dirInfo) { foreach (var d in dirInfo.GetFiles()) { diff --git a/src/Umbraco.Core/IO/FileSystemExtensions.cs b/src/Umbraco.Core/IO/FileSystemExtensions.cs index 6f1f47007f..23be195e4b 100644 --- a/src/Umbraco.Core/IO/FileSystemExtensions.cs +++ b/src/Umbraco.Core/IO/FileSystemExtensions.cs @@ -1,5 +1,7 @@ -using System; +using System; using System.IO; +using System.Security.Cryptography; +using System.Text; using System.Threading; using Umbraco.Cms.Core.IO; @@ -7,6 +9,25 @@ namespace Umbraco.Extensions { public static class FileSystemExtensions { + public static string GetStreamHash(this Stream fileStream) + { + if (fileStream.CanSeek) + { + fileStream.Seek(0, SeekOrigin.Begin); + } + + using HashAlgorithm alg = SHA1.Create(); + + // create a string output for the hash + var stringBuilder = new StringBuilder(); + var hashedByteArray = alg.ComputeHash(fileStream); + foreach (var b in hashedByteArray) + { + stringBuilder.Append(b.ToString("x2")); + } + return stringBuilder.ToString(); + } + /// /// Attempts to open the file at filePath up to maxRetries times, /// with a thread sleep time of sleepPerRetryInMilliseconds between retries. diff --git a/src/Umbraco.Tests.Integration/TestServerTest/UmbracoTestServerTestBase.cs b/src/Umbraco.Tests.Integration/TestServerTest/UmbracoTestServerTestBase.cs index b91a034420..59b970ebbc 100644 --- a/src/Umbraco.Tests.Integration/TestServerTest/UmbracoTestServerTestBase.cs +++ b/src/Umbraco.Tests.Integration/TestServerTest/UmbracoTestServerTestBase.cs @@ -134,10 +134,8 @@ namespace Umbraco.Cms.Tests.Integration.TestServerTest public override void ConfigureServices(IServiceCollection services) { services.AddTransient(); - IWebHostEnvironment webHostEnvironment = TestHelper.GetWebHostEnvironment(); TypeLoader typeLoader = services.AddTypeLoader( GetType().Assembly, - webHostEnvironment, TestHelper.GetHostingEnvironment(), TestHelper.ConsoleLoggerFactory, AppCaches.NoCache, diff --git a/src/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTest.cs b/src/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTest.cs index 9629375553..b3a168e119 100644 --- a/src/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTest.cs +++ b/src/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTest.cs @@ -204,7 +204,6 @@ namespace Umbraco.Cms.Tests.Integration.Testing // Add it! TypeLoader typeLoader = services.AddTypeLoader( GetType().Assembly, - webHostEnvironment, TestHelper.GetHostingEnvironment(), TestHelper.ConsoleLoggerFactory, AppCaches.NoCache, diff --git a/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilderExtensions.cs b/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilderExtensions.cs index e329d9fb67..b4273446f9 100644 --- a/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilderExtensions.cs +++ b/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilderExtensions.cs @@ -108,7 +108,7 @@ namespace Umbraco.Extensions services.AddUnique(profiler); ILoggerFactory loggerFactory = LoggerFactory.Create(cfg => cfg.AddSerilog(Log.Logger, false)); - TypeLoader typeLoader = services.AddTypeLoader(Assembly.GetEntryAssembly(), webHostEnvironment, tempHostingEnvironment, loggerFactory, appCaches, config, profiler); + TypeLoader typeLoader = services.AddTypeLoader(Assembly.GetEntryAssembly(), tempHostingEnvironment, loggerFactory, appCaches, config, profiler); // adds the umbraco startup filter which will call UseUmbraco early on before // other start filters are applied (depending on the ordering of IStartupFilters in DI). diff --git a/src/Umbraco.Web.Common/Extensions/ServiceCollectionExtensions.cs b/src/Umbraco.Web.Common/Extensions/ServiceCollectionExtensions.cs index 7fe337a9f9..16a83543ef 100644 --- a/src/Umbraco.Web.Common/Extensions/ServiceCollectionExtensions.cs +++ b/src/Umbraco.Web.Common/Extensions/ServiceCollectionExtensions.cs @@ -63,20 +63,29 @@ namespace Umbraco.Extensions internal static ITypeFinder AddTypeFinder( this IServiceCollection services, ILoggerFactory loggerFactory, - IWebHostEnvironment webHostEnvironment, Assembly entryAssembly, IConfiguration config, IProfilingLogger profilingLogger) { + TypeFinderSettings typeFinderSettings = config.GetSection(Cms.Core.Constants.Configuration.ConfigTypeFinder).Get() ?? new TypeFinderSettings(); - var typeFinderSettings = config.GetSection(Cms.Core.Constants.Configuration.ConfigTypeFinder).Get() ?? new TypeFinderSettings(); + var assemblyProvider = new DefaultUmbracoAssemblyProvider(entryAssembly, loggerFactory); + + var runtimeHashPaths = new RuntimeHashPaths(); + foreach(Assembly assembly in assemblyProvider.Assemblies) + { + // TODO: We need to test this on a published website + if (!assembly.IsDynamic && assembly.Location != null) + { + runtimeHashPaths.AddFile(new FileInfo(assembly.Location)); + } + } - var runtimeHashPaths = new RuntimeHashPaths().AddFolder(new DirectoryInfo(Path.Combine(webHostEnvironment.ContentRootPath, "bin"))); var runtimeHash = new RuntimeHash(profilingLogger, runtimeHashPaths); var typeFinder = new TypeFinder( loggerFactory.CreateLogger(), - new DefaultUmbracoAssemblyProvider(entryAssembly, loggerFactory), + assemblyProvider, runtimeHash, new TypeFinderConfig(Options.Create(typeFinderSettings)) ); @@ -89,7 +98,6 @@ namespace Umbraco.Extensions public static TypeLoader AddTypeLoader( this IServiceCollection services, Assembly entryAssembly, - IWebHostEnvironment webHostEnvironment, IHostingEnvironment hostingEnvironment, ILoggerFactory loggerFactory, AppCaches appCaches, @@ -97,7 +105,7 @@ namespace Umbraco.Extensions IProfiler profiler) { var profilingLogger = new ProfilingLogger(loggerFactory.CreateLogger(), profiler); - var typeFinder = services.AddTypeFinder(loggerFactory, webHostEnvironment, entryAssembly, configuration, profilingLogger); + var typeFinder = services.AddTypeFinder(loggerFactory, entryAssembly, configuration, profilingLogger); var typeLoader = new TypeLoader( typeFinder,