diff --git a/src/ImageProcessor.Web/NET45/Caching/DiskCache.cs b/src/ImageProcessor.Web/NET45/Caching/DiskCache.cs index 911441ebd..7e30aaf9a 100644 --- a/src/ImageProcessor.Web/NET45/Caching/DiskCache.cs +++ b/src/ImageProcessor.Web/NET45/Caching/DiskCache.cs @@ -11,6 +11,7 @@ namespace ImageProcessor.Web.Caching using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; + using System.Drawing; using System.Globalization; using System.IO; using System.Linq; @@ -19,6 +20,7 @@ namespace ImageProcessor.Web.Caching using System.Web; using System.Web.Hosting; using ImageProcessor.Helpers.Extensions; + using ImageProcessor.Imaging; using ImageProcessor.Web.Config; using ImageProcessor.Web.Helpers; #endregion @@ -429,7 +431,7 @@ namespace ImageProcessor.Web.Caching // Use an md5 hash of the full path including the querystring to create the image name. // That name can also be used as a key for the cached image and we should be able to use // The first character of that hash as a subfolder. - string parsedExtension = this.ParseExtension(this.fullPath); + string parsedExtension = ImageUtils.GetExtension(this.fullPath); string fallbackExtension = this.imageName.Substring(this.imageName.LastIndexOf(".", StringComparison.Ordinal) + 1); string encryptedName = this.fullPath.ToMD5Fingerprint(); string firstSubpath = encryptedName.Substring(0, 1); @@ -438,7 +440,7 @@ namespace ImageProcessor.Web.Caching string cachedFileName = string.Format( "{0}.{1}", encryptedName, - !string.IsNullOrWhiteSpace(parsedExtension) ? parsedExtension : fallbackExtension); + !string.IsNullOrWhiteSpace(parsedExtension) ? parsedExtension.Replace(".", string.Empty) : fallbackExtension); cachedPath = Path.Combine(AbsoluteCachePath, firstSubpath, secondSubpath, cachedFileName); } @@ -446,22 +448,6 @@ namespace ImageProcessor.Web.Caching return cachedPath; } - /// - /// Returns the correct file extension for the given string input - /// - /// - /// The string to parse. - /// - /// - /// The correct file extension for the given string input if it can find one; otherwise an empty string. - /// - private string ParseExtension(string input) - { - Match match = FormatRegex.Match(input); - - return match.Success ? match.Value : string.Empty; - } - /// /// The rough date time compare. /// diff --git a/src/ImageProcessor.Web/NET45/Caching/SQLContext.cs b/src/ImageProcessor.Web/NET45/Caching/SQLContext.cs index 4d2bb3983..fe1da938d 100644 --- a/src/ImageProcessor.Web/NET45/Caching/SQLContext.cs +++ b/src/ImageProcessor.Web/NET45/Caching/SQLContext.cs @@ -73,9 +73,9 @@ namespace ImageProcessor.Web.Caching } } } - catch (Exception ex) + catch { - throw ex; + throw; } } diff --git a/src/ImageProcessor.Web/NET45/HttpModules/ImageProcessingModule.cs b/src/ImageProcessor.Web/NET45/HttpModules/ImageProcessingModule.cs index 74e5df1e9..2cd0d3b1f 100644 --- a/src/ImageProcessor.Web/NET45/HttpModules/ImageProcessingModule.cs +++ b/src/ImageProcessor.Web/NET45/HttpModules/ImageProcessingModule.cs @@ -12,10 +12,15 @@ namespace ImageProcessor.Web.HttpModules using System.IO; using System.Net; using System.Reflection; + using System.Security; + using System.Security.Permissions; + using System.Security.Principal; using System.Threading; using System.Threading.Tasks; using System.Web; using System.Web.Hosting; + using System.Web.Security; + using ImageProcessor.Helpers.Extensions; using ImageProcessor.Imaging; using ImageProcessor.Web.Caching; @@ -74,16 +79,15 @@ namespace ImageProcessor.Web.HttpModules #if NET45 - EventHandlerTaskAsyncHelper wrapper = new EventHandlerTaskAsyncHelper(this.ContextBeginRequest); - context.AddOnBeginRequestAsync(wrapper.BeginEventHandler, wrapper.EndEventHandler); + EventHandlerTaskAsyncHelper wrapper = new EventHandlerTaskAsyncHelper(this.PostAuthorizeRequest); + context.AddOnPostAuthorizeRequestAsync(wrapper.BeginEventHandler, wrapper.EndEventHandler); #else - context.BeginRequest += this.ContextBeginRequest; + context.PostAuthorizeRequest += this.PostAuthorizeRequest; #endif - context.PreSendRequestHeaders += this.ContextPreSendRequestHeaders; } @@ -99,7 +103,7 @@ namespace ImageProcessor.Web.HttpModules #if NET45 /// - /// Occurs as the first event in the HTTP pipeline chain of execution when ASP.NET responds to a request. + /// Occurs when the user for the current request has been authorized. /// /// /// The source of the event. @@ -110,7 +114,7 @@ namespace ImageProcessor.Web.HttpModules /// /// The . /// - private Task ContextBeginRequest(object sender, EventArgs e) + private Task PostAuthorizeRequest(object sender, EventArgs e) { HttpContext context = ((HttpApplication)sender).Context; return this.ProcessImageAsync(context); @@ -119,11 +123,11 @@ namespace ImageProcessor.Web.HttpModules #else /// - /// Occurs as the first event in the HTTP pipeline chain of execution when ASP.NET responds to a request. + /// Occurs when the user for the current request has been authorized. /// /// The source of the event. /// An EventArgs that contains the event data. - private async void ContextBeginRequest(object sender, EventArgs e) + private async void PostAuthorizeRequest(object sender, EventArgs e) { HttpContext context = ((HttpApplication)sender).Context; await this.ProcessImageAsync(context); @@ -211,73 +215,103 @@ namespace ImageProcessor.Web.HttpModules // Create a new cache to help process and cache the request. DiskCache cache = new DiskCache(request, requestPath, fullPath, imageName, isRemote); - // Is the file new or updated? - bool isNewOrUpdated = await cache.IsNewOrUpdatedFileAsync(); + // Since we are now rewriting the path we need to check again that the current user has access + // to the rewritten path. + // Get the user for the current request + // If the user is anonymous or authentication doesn't work for this suffix avoid a NullReferenceException + // in the UrlAuthorizationModule by creating a generic identity. + string virtualCachedPath = cache.GetVirtualCachedPath(); + + IPrincipal user = context.User + ?? new GenericPrincipal(new GenericIdentity(string.Empty, string.Empty), new string[0]); + + // Do we have permission to call UrlAuthorizationModule.CheckUrlAccessForPrincipal? + PermissionSet permission = new PermissionSet(PermissionState.None); + permission.AddPermission(new AspNetHostingPermission(AspNetHostingPermissionLevel.Unrestricted)); + bool hasPermission = permission.IsSubsetOf(AppDomain.CurrentDomain.PermissionSet); + + bool isAllowed = true; - // Only process if the file has been updated. - if (isNewOrUpdated) + // Run the rewritten path past the auth system again, using the result as the default "AllowAccess" value + if (hasPermission && !context.SkipAuthorization) { - // Process the image. - using (ImageFactory imageFactory = new ImageFactory()) + isAllowed = UrlAuthorizationModule.CheckUrlAccessForPrincipal(virtualCachedPath, user, "GET"); + } + + if (isAllowed) + { + // Is the file new or updated? + bool isNewOrUpdated = await cache.IsNewOrUpdatedFileAsync(); + + // Only process if the file has been updated. + if (isNewOrUpdated) { - if (isRemote) + // Process the image. + using (ImageFactory imageFactory = new ImageFactory()) { - Uri uri = new Uri(requestPath); + if (isRemote) + { + Uri uri = new Uri(requestPath); - RemoteFile remoteFile = new RemoteFile(uri, false); + RemoteFile remoteFile = new RemoteFile(uri, false); - // Prevent response blocking. - WebResponse webResponse = await remoteFile.GetWebResponseAsync().ConfigureAwait(false); + // Prevent response blocking. + WebResponse webResponse = await remoteFile.GetWebResponseAsync().ConfigureAwait(false); - using (MemoryStream memoryStream = new MemoryStream()) - { - using (WebResponse response = webResponse) + using (MemoryStream memoryStream = new MemoryStream()) { - using (Stream responseStream = response.GetResponseStream()) + using (WebResponse response = webResponse) { - if (responseStream != null) + using (Stream responseStream = response.GetResponseStream()) { - // Trim the cache. - await cache.TrimCachedFoldersAsync(); + if (responseStream != null) + { + // Trim the cache. + await cache.TrimCachedFoldersAsync(); - responseStream.CopyTo(memoryStream); + responseStream.CopyTo(memoryStream); - imageFactory.Load(memoryStream) - .AddQueryString(queryString) - .Format(ImageUtils.GetImageFormat(imageName)) - .AutoProcess().Save(cache.CachedPath); + imageFactory.Load(memoryStream) + .AddQueryString(queryString) + .Format(ImageUtils.GetImageFormat(imageName)) + .AutoProcess().Save(cache.CachedPath); - // Ensure that the LastWriteTime property of the source and cached file match. - DateTime dateTime = await cache.SetCachedLastWriteTimeAsync(); + // Ensure that the LastWriteTime property of the source and cached file match. + DateTime dateTime = await cache.SetCachedLastWriteTimeAsync(); - // Add to the cache. - await cache.AddImageToCacheAsync(dateTime); + // Add to the cache. + await cache.AddImageToCacheAsync(dateTime); + } } } } } - } - else - { - // Trim the cache. - await cache.TrimCachedFoldersAsync(); + else + { + // Trim the cache. + await cache.TrimCachedFoldersAsync(); - imageFactory.Load(fullPath).AutoProcess().Save(cache.CachedPath); + imageFactory.Load(fullPath).AutoProcess().Save(cache.CachedPath); - // Ensure that the LastWriteTime property of the source and cached file match. - DateTime dateTime = await cache.SetCachedLastWriteTimeAsync(); + // Ensure that the LastWriteTime property of the source and cached file match. + DateTime dateTime = await cache.SetCachedLastWriteTimeAsync(); - // Add to the cache. - await cache.AddImageToCacheAsync(dateTime); + // Add to the cache. + await cache.AddImageToCacheAsync(dateTime); + } } } - } - // Store the response type in the context for later retrieval. - context.Items[CachedResponseTypeKey] = ImageUtils.GetResponseType(fullPath).ToDescription(); + // Store the response type in the context for later retrieval. + context.Items[CachedResponseTypeKey] = ImageUtils.GetResponseType(fullPath).ToDescription(); - // The cached file is valid so just rewrite the path. - context.RewritePath(cache.GetVirtualCachedPath(), false); + // The cached file is valid so just rewrite the path. + context.RewritePath(cache.GetVirtualCachedPath(), false); + } + else + { + throw new HttpException(403, "Access denied"); + } } } diff --git a/src/ImageProcessor.Web/NET45/Properties/AssemblyInfo.cs b/src/ImageProcessor.Web/NET45/Properties/AssemblyInfo.cs index 210cf2a6c..475842401 100644 --- a/src/ImageProcessor.Web/NET45/Properties/AssemblyInfo.cs +++ b/src/ImageProcessor.Web/NET45/Properties/AssemblyInfo.cs @@ -31,5 +31,5 @@ using System.Runtime.InteropServices; // // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: -[assembly: AssemblyVersion("2.3.0.1")] -[assembly: AssemblyFileVersion("2.3.0.1")] +[assembly: AssemblyVersion("2.3.0.2")] +[assembly: AssemblyFileVersion("2.3.0.2")] diff --git a/src/ImageProcessor/Helpers/Extensions/StringExtensions.cs b/src/ImageProcessor/Helpers/Extensions/StringExtensions.cs index 47bd57bd4..6b3c1a5be 100644 --- a/src/ImageProcessor/Helpers/Extensions/StringExtensions.cs +++ b/src/ImageProcessor/Helpers/Extensions/StringExtensions.cs @@ -8,9 +8,8 @@ namespace ImageProcessor.Helpers.Extensions { #region Using - using System.Diagnostics.Contracts; + using System; using System.Globalization; - using System.IO; using System.Linq; using System.Security.Cryptography; using System.Text; @@ -116,7 +115,10 @@ namespace ImageProcessor.Helpers.Extensions /// An array of integers scraped from the String. public static int[] ToPositiveIntegerArray(this string expression) { - Contract.Requires(!string.IsNullOrWhiteSpace(expression)); + if (string.IsNullOrWhiteSpace(expression)) + { + throw new ArgumentNullException("expression"); + } Regex regex = new Regex(@"\d+", RegexOptions.Compiled); @@ -144,33 +146,9 @@ namespace ImageProcessor.Helpers.Extensions /// True if the given string is a valid virtual path name public static bool IsValidVirtualPathName(this string expression) { - // Check the start of the string. - if (expression.StartsWith("~/")) - { - // Trim the first two characters and test the path. - expression = expression.Substring(2); - return expression.IsValidPathName(); - } - - return false; - } - - /// - /// Checks the string to see whether the value is a valid path name. - /// - /// - /// For an explanation - /// - /// - /// The String instance that this method extends. - /// True if the given string is a valid path name - public static bool IsValidPathName(this string expression) - { - // Create a regex of invalid characters and test it. - string invalidPathNameChars = new string(Path.GetInvalidFileNameChars()); - Regex regFixPathName = new Regex("[" + Regex.Escape(invalidPathNameChars) + "]"); + Uri uri; - return !regFixPathName.IsMatch(expression); + return Uri.TryCreate(expression, UriKind.Relative, out uri) && uri.IsWellFormedOriginalString(); } #endregion } diff --git a/src/ImageProcessor/Imaging/ImageUtils.cs b/src/ImageProcessor/Imaging/ImageUtils.cs index fdfb0ca83..f8e69233c 100644 --- a/src/ImageProcessor/Imaging/ImageUtils.cs +++ b/src/ImageProcessor/Imaging/ImageUtils.cs @@ -17,7 +17,6 @@ namespace ImageProcessor.Imaging using System.IO; using System.Linq; using System.Text.RegularExpressions; - using System.Threading.Tasks; #endregion /// @@ -28,7 +27,7 @@ namespace ImageProcessor.Imaging /// /// The image format regex. /// - private static readonly Regex FormatRegex = new Regex(@"(\.?)(j(pg|peg)|bmp|png|gif|ti(f|ff))$", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.RightToLeft); + private static readonly Regex FormatRegex = new Regex(@"(\.?)(j(pg|peg)|bmp|png|gif|ti(f|ff))", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.RightToLeft); /// /// Returns the correct response type based on the given request path. @@ -41,25 +40,27 @@ namespace ImageProcessor.Imaging /// public static ResponseType GetResponseType(string request) { - foreach (Match match in FormatRegex.Matches(request)) + Match match = FormatRegex.Matches(request)[0]; + + switch (match.Value.ToUpperInvariant()) { - switch (match.Value.ToUpperInvariant()) - { - case "PNG": - return ResponseType.Png; - case "BMP": - return ResponseType.Bmp; - case "GIF": - return ResponseType.Gif; - case "TIF": - case "TIFF": - return ResponseType.Tiff; - default: - return ResponseType.Jpeg; - } + case "PNG": + case ".PNG": + return ResponseType.Png; + case "BMP": + case ".BMP": + return ResponseType.Bmp; + case "GIF": + case ".GIF": + return ResponseType.Gif; + case "TIF": + case "TIFF": + case ".TIF": + case ".TIFF": + return ResponseType.Tiff; + default: + return ResponseType.Jpeg; } - - return ResponseType.Jpeg; } /// @@ -91,7 +92,7 @@ namespace ImageProcessor.Imaging } } - // TODO: Show custom exception?? + // TODO: Show custom exception? return null; } @@ -115,7 +116,6 @@ namespace ImageProcessor.Imaging case "Png": return ".png"; case "Tif": - return ".tif"; case "Tiff": return ".tif"; default: @@ -197,9 +197,26 @@ namespace ImageProcessor.Imaging /// True the value contains a valid image extension, otherwise false. public static bool IsValidImageExtension(string fileName) { + return FormatRegex.IsMatch(fileName); } + /// + /// Returns the correct file extension for the given string input + /// + /// + /// The string to parse. + /// + /// + /// The correct file extension for the given string input if it can find one; otherwise an empty string. + /// + public static string GetExtension(string input) + { + Match match = FormatRegex.Matches(input)[0]; + + return match.Success ? match.Value : string.Empty; + } + /// Returns a value indicating whether or not the given bitmap is indexed. /// The image to check /// Whether or not the given bitmap is indexed. diff --git a/src/ImageProcessor/Processors/Format.cs b/src/ImageProcessor/Processors/Format.cs index 42efc8bd8..16fc08973 100644 --- a/src/ImageProcessor/Processors/Format.cs +++ b/src/ImageProcessor/Processors/Format.cs @@ -25,7 +25,7 @@ namespace ImageProcessor.Processors /// /// The regular expression to search strings for. /// - private static readonly Regex QueryRegex = new Regex(@"format=(jpeg|png|png8|bmp|gif|tif)", RegexOptions.Compiled); + private static readonly Regex QueryRegex = new Regex(@"format=(j(pg|peg)|png|png8|bmp|gif|tif)", RegexOptions.Compiled); #region IGraphicsProcessor Members /// diff --git a/src/ImageProcessor/Properties/AssemblyInfo.cs b/src/ImageProcessor/Properties/AssemblyInfo.cs index d25387498..68075c37d 100644 --- a/src/ImageProcessor/Properties/AssemblyInfo.cs +++ b/src/ImageProcessor/Properties/AssemblyInfo.cs @@ -32,6 +32,6 @@ using System.Security; // // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: -[assembly: AssemblyVersion("1.7.0.0")] -[assembly: AssemblyFileVersion("1.7.0.0")] +[assembly: AssemblyVersion("1.7.0.1")] +[assembly: AssemblyFileVersion("1.7.0.1")] diff --git a/src/Nuget/ImageProcessor.1.6.0.1.nupkg b/src/Nuget/ImageProcessor.1.6.0.1.nupkg deleted file mode 100644 index 9a61f73a9..000000000 Binary files a/src/Nuget/ImageProcessor.1.6.0.1.nupkg and /dev/null differ diff --git a/src/Nuget/ImageProcessor.1.7.0.1.nupkg b/src/Nuget/ImageProcessor.1.7.0.1.nupkg new file mode 100644 index 000000000..3d5bdf876 Binary files /dev/null and b/src/Nuget/ImageProcessor.1.7.0.1.nupkg differ diff --git a/src/Nuget/ImageProcessor.Web.2.2.3.3.nupkg.REMOVED.git-id b/src/Nuget/ImageProcessor.Web.2.2.3.3.nupkg.REMOVED.git-id deleted file mode 100644 index 9e9ad0766..000000000 --- a/src/Nuget/ImageProcessor.Web.2.2.3.3.nupkg.REMOVED.git-id +++ /dev/null @@ -1 +0,0 @@ -01e1999a7804bb48ba37f247dfdb1bf01f05fa62 \ No newline at end of file diff --git a/src/Nuget/ImageProcessor.Web.2.3.0.0.nupkg.REMOVED.git-id b/src/Nuget/ImageProcessor.Web.2.3.0.0.nupkg.REMOVED.git-id deleted file mode 100644 index b2911f9c5..000000000 --- a/src/Nuget/ImageProcessor.Web.2.3.0.0.nupkg.REMOVED.git-id +++ /dev/null @@ -1 +0,0 @@ -8b33cb0b4f13802b62d2511239e212680ad67158 \ No newline at end of file diff --git a/src/Nuget/ImageProcessor.Web.2.3.0.2.nupkg.REMOVED.git-id b/src/Nuget/ImageProcessor.Web.2.3.0.2.nupkg.REMOVED.git-id new file mode 100644 index 000000000..10b435c4e --- /dev/null +++ b/src/Nuget/ImageProcessor.Web.2.3.0.2.nupkg.REMOVED.git-id @@ -0,0 +1 @@ +8c5a374f583194706fa94a269f7ab4543dc4ece6 \ No newline at end of file diff --git a/src/Nuget/ImageProcessor.Web.2.3.0.3.nupkg.REMOVED.git-id b/src/Nuget/ImageProcessor.Web.2.3.0.3.nupkg.REMOVED.git-id new file mode 100644 index 000000000..a026a9cf9 --- /dev/null +++ b/src/Nuget/ImageProcessor.Web.2.3.0.3.nupkg.REMOVED.git-id @@ -0,0 +1 @@ +0a367af8c6588fe2be54ef5950820a7f06f7b0f1 \ No newline at end of file diff --git a/src/TestWebsites/NET45/Test_Website_NET45/Web.config b/src/TestWebsites/NET45/Test_Website_NET45/Web.config index 093761644..fb3bbb368 100644 --- a/src/TestWebsites/NET45/Test_Website_NET45/Web.config +++ b/src/TestWebsites/NET45/Test_Website_NET45/Web.config @@ -5,79 +5,80 @@ --> - - -
-
-
- - + + +
+
+
+ + - - - - - - - + + + + + + + - + - + - + - - - - - - - - - - + + + + + + + + + + - - - - + + + - - + - - - - - - - - + + - - - + + + + + + + + - + + + - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + +