From d0877daa9d71812670c9a817aa5f5b84aafdd3d8 Mon Sep 17 00:00:00 2001 From: PhyxionNL <7643972+PhyxionNL@users.noreply.github.com> Date: Fri, 10 Feb 2023 17:23:24 +0100 Subject: [PATCH 1/3] Check for invalid characters + Add compression level to bundles. --- .../Cache/PhysicalFileCacheFileSystem.cs | 3 +- src/Smidge.Core/CompositeFiles/Compressor.cs | 8 ++-- .../CompositeFiles/DefaultUrlManager.cs | 47 +++++++++---------- src/Smidge.Core/CompressionType.cs | 12 +++-- .../FileProcessors/PreProcessManager.cs | 10 +++- src/Smidge.Core/Options/BundleOptions.cs | 8 +++- .../CompositeFileCacheFilterAttribute.cs | 6 +-- src/Smidge/Controllers/SmidgeController.cs | 16 +++---- src/Smidge/Models/RequestModel.cs | 2 +- src/Smidge/RequestHelper.cs | 4 +- 10 files changed, 64 insertions(+), 52 deletions(-) diff --git a/src/Smidge.Core/Cache/PhysicalFileCacheFileSystem.cs b/src/Smidge.Core/Cache/PhysicalFileCacheFileSystem.cs index 2ba4cab..dfc8070 100644 --- a/src/Smidge.Core/Cache/PhysicalFileCacheFileSystem.cs +++ b/src/Smidge.Core/Cache/PhysicalFileCacheFileSystem.cs @@ -48,8 +48,7 @@ public IFileInfo GetRequiredFileInfo(string filePath) return fileInfo; } - private string GetCompositeFilePath(string cacheBusterValue, CompressionType type, string filesetKey) - => $"{cacheBusterValue}/{type}/{filesetKey + ".s"}"; + private string GetCompositeFilePath(string cacheBusterValue, CompressionType type, string filesetKey) => $"{cacheBusterValue}/{type}/{filesetKey}.s"; public Task ClearCachedCompositeFileAsync(string cacheBusterValue, CompressionType type, string filesetKey) { diff --git a/src/Smidge.Core/CompositeFiles/Compressor.cs b/src/Smidge.Core/CompositeFiles/Compressor.cs index c44aa81..4d358ea 100644 --- a/src/Smidge.Core/CompositeFiles/Compressor.cs +++ b/src/Smidge.Core/CompositeFiles/Compressor.cs @@ -11,7 +11,7 @@ namespace Smidge.CompositeFiles /// public static class Compressor { - public static async Task CompressAsync(CompressionType type, Stream original) + public static async Task CompressAsync(CompressionType type, CompressionLevel level, Stream original) { using (var ms = new MemoryStream()) { @@ -19,15 +19,15 @@ public static async Task CompressAsync(CompressionType type, Stream orig if (type == CompressionType.Deflate) { - compressedStream = new DeflateStream(ms, CompressionLevel.Optimal); + compressedStream = new DeflateStream(ms, level); } else if (type == CompressionType.GZip) { - compressedStream = new GZipStream(ms, CompressionLevel.Optimal); + compressedStream = new GZipStream(ms, level); } else if (type == CompressionType.Brotli) { - compressedStream = new BrotliStream(ms, CompressionLevel.Optimal); + compressedStream = new BrotliStream(ms, level); } if (type != CompressionType.None) diff --git a/src/Smidge.Core/CompositeFiles/DefaultUrlManager.cs b/src/Smidge.Core/CompositeFiles/DefaultUrlManager.cs index 2437b7a..eed91dd 100644 --- a/src/Smidge.Core/CompositeFiles/DefaultUrlManager.cs +++ b/src/Smidge.Core/CompositeFiles/DefaultUrlManager.cs @@ -12,16 +12,16 @@ namespace Smidge.CompositeFiles public class DefaultUrlManager : IUrlManager { private readonly IHasher _hasher; - private readonly IRequestHelper _requestHelper; + private readonly bool _keepFileExtensions; private readonly UrlManagerOptions _options; - private readonly ISmidgeConfig _config; + private readonly IRequestHelper _requestHelper; public DefaultUrlManager(IOptions options, IHasher hasher, IRequestHelper requestHelper, ISmidgeConfig config) { _hasher = hasher; _requestHelper = requestHelper; _options = options.Value.UrlOptions; - _config = config; + _keepFileExtensions = config.KeepFileExtensions; } public string AppendCacheBuster(string url, bool debug, string cacheBusterValue) @@ -42,16 +42,13 @@ public string GetUrl(string bundleName, string fileExtension, bool debug, string throw new ArgumentException($"'{nameof(cacheBusterValue)}' cannot be null or whitespace.", nameof(cacheBusterValue)); } - string handler = _config.KeepFileExtensions ? "~/{0}/{1}.{3}{4}{2}" : "~/{0}/{1}{2}.{3}{4}"; - return _requestHelper.Content( - string.Format( - handler, - _options.BundleFilePath, - Uri.EscapeUriString(bundleName), - fileExtension, - debug ? 'd' : 'v', - cacheBusterValue)); - + var handler = _keepFileExtensions ? "~/{0}/{1}.{3}{4}{2}" : "~/{0}/{1}{2}.{3}{4}"; + return _requestHelper.Content(string.Format(handler, + _options.BundleFilePath, + Uri.EscapeUriString(bundleName), + fileExtension, + debug ? 'd' : 'v', + cacheBusterValue)); } public IEnumerable GetUrls(IEnumerable dependencies, string fileExtension, string cacheBusterValue) @@ -67,7 +64,7 @@ public IEnumerable GetUrls(IEnumerable dependencies, strin var builderCount = 1; var remaining = new Queue(dependencies); - while (remaining.Any()) + while (remaining.Count > 0) { var current = remaining.Peek(); @@ -137,23 +134,25 @@ public ParsedUrlPath ParsePath(string input) } //can start with 'v' or 'd' (d == debug) - var prefix = _config.KeepFileExtensions ? parts[parts.Length - 2][0] : parts[parts.Length - 1][0]; + var prefix = _keepFileExtensions ? parts[parts.Length - 2][0] : parts[parts.Length - 1][0]; if (prefix != 'v' && prefix != 'd') { //invalid return null; } result.Debug = prefix == 'd'; - - result.CacheBusterValue = _config.KeepFileExtensions ? parts[parts.Length - 2].Substring(1) : parts[parts.Length - 1].Substring(1); - var ext = _config.KeepFileExtensions ? parts[parts.Length - 1] : parts[parts.Length - 2]; - if (!Enum.TryParse(ext, true, out WebFileType type)) - { - //invalid + result.CacheBusterValue = _keepFileExtensions ? parts[parts.Length - 2].Substring(1) : parts[parts.Length - 1].Substring(1); + var ext = _keepFileExtensions ? parts[parts.Length - 1] : parts[parts.Length - 2]; + + WebFileType type; + if (ext.Equals("js", StringComparison.OrdinalIgnoreCase)) + type = WebFileType.Js; + else if (ext.Equals("css", StringComparison.OrdinalIgnoreCase)) + type = WebFileType.Css; + else return null; - } - result.WebType = type; + result.WebType = type; result.Names = parts.Take(parts.Length - 2); return result; @@ -163,7 +162,7 @@ private string GetCompositeUrl(string fileKey, string fileExtension, string cach { //Create a delimited URL query string - string handler = _config.KeepFileExtensions ? "~/{0}/{1}.v{3}{2}" : "~/{0}/{1}{2}.v{3}"; + string handler = _keepFileExtensions ? "~/{0}/{1}.v{3}{2}" : "~/{0}/{1}{2}.v{3}"; return _requestHelper.Content( string.Format( handler, diff --git a/src/Smidge.Core/CompressionType.cs b/src/Smidge.Core/CompressionType.cs index 59f1484..4c73766 100644 --- a/src/Smidge.Core/CompressionType.cs +++ b/src/Smidge.Core/CompressionType.cs @@ -18,11 +18,13 @@ public struct CompressionType : IEquatable, IEquatable public static CompressionType Parse(string compressionType) { - if (compressionType == Deflate) return Deflate; - if (compressionType == GZip) return GZip; - if (compressionType == "x-gzip") return GZip; - if (compressionType == Brotli) return Brotli; - return None; + if (compressionType == Brotli) + return Brotli; + + if ((compressionType == GZip) || (compressionType == "x-gzip")) + return GZip; + + return compressionType == Deflate ? Deflate : None; } public override string ToString() => _compressionType; diff --git a/src/Smidge.Core/FileProcessors/PreProcessManager.cs b/src/Smidge.Core/FileProcessors/PreProcessManager.cs index 181259f..5d6f8fb 100644 --- a/src/Smidge.Core/FileProcessors/PreProcessManager.cs +++ b/src/Smidge.Core/FileProcessors/PreProcessManager.cs @@ -20,6 +20,7 @@ public class PreProcessManager : IPreProcessManager private readonly IBundleManager _bundleManager; private readonly ILogger _logger; private readonly SemaphoreSlim _processFileSemaphore = new SemaphoreSlim(1, 1); + private readonly char[] _invalidPathChars = Path.GetInvalidPathChars(); public PreProcessManager(ISmidgeFileSystem fileSystem, IBundleManager bundleManager, ILogger logger) { @@ -43,7 +44,12 @@ private async Task ProcessFile(IWebFile file, BundleOptions bundleOptions, Bundl if (file.FilePath.Contains(SmidgeConstants.SchemeDelimiter)) { throw new InvalidOperationException("Cannot process an external file as part of a bundle"); - }; + } + + if (file.FilePath.IndexOfAny(_invalidPathChars) != -1) + { + throw new InvalidOperationException("Cannot process paths with invalid chars"); + } await _processFileSemaphore.WaitAsync(); @@ -65,7 +71,7 @@ private async Task ProcessFileImpl(IWebFile file, BundleOptions bundleOptions, B var extension = Path.GetExtension(file.FilePath); - var fileWatchEnabled = bundleOptions?.FileWatchOptions.Enabled ?? false; + var fileWatchEnabled = bundleOptions.FileWatchOptions.Enabled; var cacheBusterValue = bundleContext.CacheBusterValue; diff --git a/src/Smidge.Core/Options/BundleOptions.cs b/src/Smidge.Core/Options/BundleOptions.cs index 079527f..3b50b0c 100644 --- a/src/Smidge.Core/Options/BundleOptions.cs +++ b/src/Smidge.Core/Options/BundleOptions.cs @@ -1,4 +1,5 @@ using System; +using System.IO.Compression; using Smidge.Cache; namespace Smidge.Options @@ -19,7 +20,7 @@ public BundleOptions() CacheControlOptions = new CacheControlOptions(); ProcessAsCompositeFile = true; CompressResult = true; - + CompressionLevel = CompressionLevel.Optimal; } private Type _defaultCacheBuster; @@ -80,6 +81,11 @@ public Type GetCacheBusterType() /// public bool CompressResult { get; set; } + /// + /// The compression level of the bundle + /// + public CompressionLevel CompressionLevel {get; set; } + /// /// Used to control the caching of the bundle /// diff --git a/src/Smidge/Controllers/CompositeFileCacheFilterAttribute.cs b/src/Smidge/Controllers/CompositeFileCacheFilterAttribute.cs index cb67264..9f4fe79 100644 --- a/src/Smidge/Controllers/CompositeFileCacheFilterAttribute.cs +++ b/src/Smidge/Controllers/CompositeFileCacheFilterAttribute.cs @@ -1,4 +1,4 @@ -using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.DependencyInjection; using Smidge.Models; using System; @@ -28,7 +28,7 @@ public IFilterMetadata CreateInstance(IServiceProvider serviceProvider) internal static bool TryGetCachedCompositeFileResult(ISmidgeFileSystem fileSystem, string cacheBusterValue, string filesetKey, CompressionType type, string mime, out FileResult result, out DateTime lastWriteTime) { result = null; - lastWriteTime = DateTime.Now; + lastWriteTime = DateTime.MinValue; var cacheFile = fileSystem.CacheFileSystem.GetCachedCompositeFile(cacheBusterValue, type, filesetKey, out _); if (cacheFile.Exists) @@ -84,4 +84,4 @@ public void OnActionExecuted(ActionExecutedContext context) { } } } -} \ No newline at end of file +} diff --git a/src/Smidge/Controllers/SmidgeController.cs b/src/Smidge/Controllers/SmidgeController.cs index d053ad8..54a2dd2 100644 --- a/src/Smidge/Controllers/SmidgeController.cs +++ b/src/Smidge/Controllers/SmidgeController.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.IO; +using System.IO.Compression; using System.Linq; using System.Threading.Tasks; using Microsoft.Extensions.FileProviders; @@ -133,10 +134,10 @@ public async Task Bundle( using (var resultStream = await GetCombinedStreamAsync(fileInfos, bundleContext)) { //compress the response (if enabled) - var compressedStream = await Compressor.CompressAsync( - //do not compress anything if it's not enabled in the bundle options - bundleOptions.CompressResult ? bundleModel.Compression : CompressionType.None, - resultStream); + //do not compress anything if it's not enabled in the bundle options + var compressedStream = await Compressor.CompressAsync(bundleOptions.CompressResult ? bundleModel.Compression : CompressionType.None, + bundleOptions.CompressionLevel, + resultStream); //save the resulting compressed file, if compression is not enabled it will just save the non compressed format // this persisted file will be used in the CheckNotModifiedAttribute which will short circuit the request and return @@ -164,7 +165,6 @@ public async Task Composite( return NotFound(); } - var defaultBundleOptions = _bundleManager.GetDefaultBundleOptions(false); var cacheBusterValue = file.ParsedPath.CacheBusterValue; var cacheFile = _fileSystem.CacheFileSystem.GetCachedCompositeFile(cacheBusterValue, file.Compression, file.FileKey, out var cacheFilePath); @@ -180,8 +180,8 @@ public async Task Composite( } else { - return File(cacheFile.CreateReadStream(), file.Mime); - } + return File(cacheFile.CreateReadStream(), file.Mime); + } } using (var bundleContext = new BundleContext(cacheBusterValue, file, cacheFilePath)) @@ -192,7 +192,7 @@ public async Task Composite( using (var resultStream = await GetCombinedStreamAsync(files, bundleContext)) { - var compressedStream = await Compressor.CompressAsync(file.Compression, resultStream); + var compressedStream = await Compressor.CompressAsync(file.Compression, CompressionLevel.Optimal, resultStream); await CacheCompositeFileAsync(_fileSystem.CacheFileSystem, cacheFilePath, compressedStream); diff --git a/src/Smidge/Models/RequestModel.cs b/src/Smidge/Models/RequestModel.cs index 5c2860b..9704611 100644 --- a/src/Smidge/Models/RequestModel.cs +++ b/src/Smidge/Models/RequestModel.cs @@ -17,7 +17,7 @@ protected RequestModel(string valueName, IUrlManager urlManager, IActionContextA if (requestHelper is null)throw new ArgumentNullException(nameof(requestHelper)); //default - LastFileWriteTime = DateTime.Now; + LastFileWriteTime = DateTime.MinValue; Compression = requestHelper.GetClientCompression(accessor.ActionContext.HttpContext.Request.Headers); diff --git a/src/Smidge/RequestHelper.cs b/src/Smidge/RequestHelper.cs index 4934a0f..8080596 100644 --- a/src/Smidge/RequestHelper.cs +++ b/src/Smidge/RequestHelper.cs @@ -39,7 +39,7 @@ public string Content(IWebFile file) if (file.FilePath.StartsWith("//")) { var scheme = _siteInfo.GetBaseUrl().Scheme; - return Regex.Replace(file.FilePath, @"^\/\/", string.Format("{0}{1}", scheme, SmidgeConstants.SchemeDelimiter)); + return Regex.Replace(file.FilePath, @"^\/\/", scheme + SmidgeConstants.SchemeDelimiter); } var filePath = Content(file.FilePath); @@ -71,7 +71,7 @@ public string Content(string path) if (path.StartsWith("//")) { var scheme = _siteInfo.GetBaseUrl().Scheme; - return Regex.Replace(path, @"^\/\/", string.Format("{0}{1}", scheme, SmidgeConstants.SchemeDelimiter)); + return Regex.Replace(path, @"^\/\/", scheme + SmidgeConstants.SchemeDelimiter); } //This code is taken from the UrlHelper code ... which shouldn't need to be tucked away in there From e214e101d881a0e78881b150c9ce0ee799de5f4c Mon Sep 17 00:00:00 2001 From: PhyxionNL <7643972+PhyxionNL@users.noreply.github.com> Date: Fri, 10 Feb 2023 18:06:53 +0100 Subject: [PATCH 2/3] Update SmidgeController.cs Formatting... --- src/Smidge/Controllers/SmidgeController.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Smidge/Controllers/SmidgeController.cs b/src/Smidge/Controllers/SmidgeController.cs index 54a2dd2..c036df3 100644 --- a/src/Smidge/Controllers/SmidgeController.cs +++ b/src/Smidge/Controllers/SmidgeController.cs @@ -180,8 +180,8 @@ public async Task Composite( } else { - return File(cacheFile.CreateReadStream(), file.Mime); - } + return File(cacheFile.CreateReadStream(), file.Mime); + } } using (var bundleContext = new BundleContext(cacheBusterValue, file, cacheFilePath)) @@ -241,4 +241,4 @@ private async Task GetCombinedStreamAsync(IEnumerable files, } } } -} \ No newline at end of file +} From bc08462df3e11987ab762604ea1c6660a6e9eb29 Mon Sep 17 00:00:00 2001 From: PhyxionNL <7643972+PhyxionNL@users.noreply.github.com> Date: Fri, 10 Feb 2023 18:32:56 +0100 Subject: [PATCH 3/3] Feedback. --- src/Smidge.Core/CompositeFiles/Compressor.cs | 8 +++++--- .../Controllers/CompositeFileCacheFilterAttribute.cs | 2 +- src/Smidge/Controllers/SmidgeController.cs | 8 ++++---- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/src/Smidge.Core/CompositeFiles/Compressor.cs b/src/Smidge.Core/CompositeFiles/Compressor.cs index 4d358ea..add3a05 100644 --- a/src/Smidge.Core/CompositeFiles/Compressor.cs +++ b/src/Smidge.Core/CompositeFiles/Compressor.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.IO; using System.IO.Compression; using System.Text; @@ -10,7 +10,9 @@ namespace Smidge.CompositeFiles /// Performs byte compression /// public static class Compressor - { + { + public static async Task CompressAsync(CompressionType type, Stream original) => await CompressAsync(type, CompressionLevel.Optimal, original); + public static async Task CompressAsync(CompressionType type, CompressionLevel level, Stream original) { using (var ms = new MemoryStream()) @@ -50,4 +52,4 @@ public static async Task CompressAsync(CompressionType type, Compression } } } -} \ No newline at end of file +} diff --git a/src/Smidge/Controllers/CompositeFileCacheFilterAttribute.cs b/src/Smidge/Controllers/CompositeFileCacheFilterAttribute.cs index 9f4fe79..674f7d0 100644 --- a/src/Smidge/Controllers/CompositeFileCacheFilterAttribute.cs +++ b/src/Smidge/Controllers/CompositeFileCacheFilterAttribute.cs @@ -28,7 +28,6 @@ public IFilterMetadata CreateInstance(IServiceProvider serviceProvider) internal static bool TryGetCachedCompositeFileResult(ISmidgeFileSystem fileSystem, string cacheBusterValue, string filesetKey, CompressionType type, string mime, out FileResult result, out DateTime lastWriteTime) { result = null; - lastWriteTime = DateTime.MinValue; var cacheFile = fileSystem.CacheFileSystem.GetCachedCompositeFile(cacheBusterValue, type, filesetKey, out _); if (cacheFile.Exists) @@ -48,6 +47,7 @@ internal static bool TryGetCachedCompositeFileResult(ISmidgeFileSystem fileSyste return true; } + lastWriteTime = DateTime.Now; return false; } diff --git a/src/Smidge/Controllers/SmidgeController.cs b/src/Smidge/Controllers/SmidgeController.cs index 54a2dd2..3df51b0 100644 --- a/src/Smidge/Controllers/SmidgeController.cs +++ b/src/Smidge/Controllers/SmidgeController.cs @@ -180,8 +180,8 @@ public async Task Composite( } else { - return File(cacheFile.CreateReadStream(), file.Mime); - } + return File(cacheFile.CreateReadStream(), file.Mime); + } } using (var bundleContext = new BundleContext(cacheBusterValue, file, cacheFilePath)) @@ -192,7 +192,7 @@ public async Task Composite( using (var resultStream = await GetCombinedStreamAsync(files, bundleContext)) { - var compressedStream = await Compressor.CompressAsync(file.Compression, CompressionLevel.Optimal, resultStream); + var compressedStream = await Compressor.CompressAsync(file.Compression, resultStream); await CacheCompositeFileAsync(_fileSystem.CacheFileSystem, cacheFilePath, compressedStream); @@ -241,4 +241,4 @@ private async Task GetCombinedStreamAsync(IEnumerable files, } } } -} \ No newline at end of file +}