| File: ChecksumValidator.cs | Web Access |
| Project: src\aspnetcore\src\Mvc\Mvc.Razor.RuntimeCompilation\src\Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation.csproj (Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Globalization; using System.Linq; using System.Security.Cryptography; using Microsoft.AspNetCore.Razor.Hosting; using Microsoft.AspNetCore.Razor.Language; namespace Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation; internal static class ChecksumValidator { public static bool IsRecompilationSupported(RazorCompiledItem item) { ArgumentNullException.ThrowIfNull(item); // A Razor item only supports recompilation if its primary source file has a checksum. // // Other files (view imports) may or may not have existed at the time of compilation, // so we may not have checksums for them. var checksums = item.GetChecksumMetadata(); return checksums.Any(c => string.Equals(item.Identifier, c.Identifier, StringComparison.OrdinalIgnoreCase)); } // Validates that we can use an existing precompiled view by comparing checksums with files on // disk. public static bool IsItemValid(RazorProjectFileSystem fileSystem, RazorCompiledItem item) { ArgumentNullException.ThrowIfNull(fileSystem); ArgumentNullException.ThrowIfNull(item); var checksums = item.GetChecksumMetadata(); // The checksum that matches 'Item.Identity' in this list is significant. That represents the main file. // // We don't really care about the validation unless the main file exists. This is because we expect // most sites to have some _ViewImports in common location. That means that in the case you're // using views from a 3rd party library, you'll always have **some** conflicts. // // The presence of the main file with the same content is a very strong signal that you're in a // development scenario. var primaryChecksum = checksums .FirstOrDefault(c => string.Equals(item.Identifier, c.Identifier, StringComparison.OrdinalIgnoreCase)); if (primaryChecksum == null) { // No primary checksum, assume valid. return true; } var projectItem = fileSystem.GetItem(primaryChecksum.Identifier, fileKind: null); if (!projectItem.Exists) { // Main file doesn't exist - assume valid. return true; } var sourceDocumentChecksum = ComputeChecksum(projectItem, primaryChecksum.ChecksumAlgorithm); if (!string.Equals(sourceDocumentChecksum.algorithm, primaryChecksum.ChecksumAlgorithm, StringComparison.OrdinalIgnoreCase) || !ChecksumsEqual(primaryChecksum.Checksum, sourceDocumentChecksum.checksum)) { // Main file exists, but checksums not equal. return false; } for (var i = 0; i < checksums.Count; i++) { var checksum = checksums[i]; if (string.Equals(item.Identifier, checksum.Identifier, StringComparison.OrdinalIgnoreCase)) { // Ignore primary checksum on this pass. continue; } var importItem = fileSystem.GetItem(checksum.Identifier, fileKind: null); if (!importItem.Exists) { // Import file doesn't exist - assume invalid. return false; } sourceDocumentChecksum = ComputeChecksum(importItem, checksum.ChecksumAlgorithm); if (!string.Equals(sourceDocumentChecksum.algorithm, checksum.ChecksumAlgorithm, StringComparison.OrdinalIgnoreCase) || !ChecksumsEqual(checksum.Checksum, sourceDocumentChecksum.checksum)) { // Import file exists, but checksums not equal. return false; } } return true; } private static (byte[] checksum, string algorithm) ComputeChecksum(RazorProjectItem projectItem, string checksumAlgorithm) { ArgumentNullException.ThrowIfNull(projectItem); Func<Stream, byte[]> hashData; string algorithmName; //only SHA1 and SHA256 are supported. Default to SHA1 if (nameof(SHA256).Equals(checksumAlgorithm, StringComparison.OrdinalIgnoreCase)) { hashData = SHA256.HashData; algorithmName = nameof(SHA256); } else { hashData = SHA1.HashData; algorithmName = nameof(SHA1); } using (var stream = projectItem.Read()) { return (hashData(stream), algorithmName); } } private static bool ChecksumsEqual(string checksum, byte[] bytes) { if (bytes.Length * 2 != checksum.Length) { return false; } for (var i = 0; i < bytes.Length; i++) { var text = bytes[i].ToString("x2", CultureInfo.InvariantCulture); if (checksum[i * 2] != text[0] || checksum[i * 2 + 1] != text[1]) { return false; } } return true; } }