From 5f1fb144e08093b685cf35a4266da59c71cc3b7f Mon Sep 17 00:00:00 2001 From: imranhaidercogworks Date: Sun, 30 Sep 2018 12:09:44 +0100 Subject: [PATCH] Uploading SVG's Causes Error (Depending on imageFileTypes Setting) (#3072) --- .../UmbracoSettings/IContentSection.cs | 2 +- src/Umbraco.Core/Constants-Conventions.cs | 7 ++- src/Umbraco.Core/IO/MediaFileSystem.cs | 57 ++++++++++--------- src/Umbraco.Core/Media/Exif/ImageFile.cs | 36 ++++++------ .../Media/Exif/ImageFileFormat.cs | 4 ++ src/Umbraco.Core/Media/Exif/SVGFile.cs | 37 ++++++++++++ .../Media/TypeDetector/JpegDetector.cs | 14 +++++ .../TypeDetector/RasterizedTypeDetector.cs | 16 ++++++ .../Media/TypeDetector/SVGDetector.cs | 24 ++++++++ .../Media/TypeDetector/TIFFDetector.cs | 24 ++++++++ src/Umbraco.Core/Umbraco.Core.csproj | 5 ++ .../imaging/umbimagegravity.directive.js | 36 +++++++----- .../imagecropper/imagecropper.html | 4 +- .../config/umbracoSettings.config | 4 +- src/Umbraco.Web/Editors/MediaController.cs | 2 +- .../FileUploadPropertyValueEditor.cs | 4 +- .../ImageCropperPropertyValueEditor.cs | 2 +- src/umbraco.businesslogic/IO/IOHelper.cs | 32 +++++------ 18 files changed, 225 insertions(+), 85 deletions(-) create mode 100644 src/Umbraco.Core/Media/Exif/SVGFile.cs create mode 100644 src/Umbraco.Core/Media/TypeDetector/JpegDetector.cs create mode 100644 src/Umbraco.Core/Media/TypeDetector/RasterizedTypeDetector.cs create mode 100644 src/Umbraco.Core/Media/TypeDetector/SVGDetector.cs create mode 100644 src/Umbraco.Core/Media/TypeDetector/TIFFDetector.cs diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/IContentSection.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/IContentSection.cs index 6c0d8327f1..bfcbabaccd 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/IContentSection.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/IContentSection.cs @@ -13,7 +13,7 @@ namespace Umbraco.Core.Configuration.UmbracoSettings IEnumerable ImageTagAllowedAttributes { get; } IEnumerable ImageAutoFillProperties { get; } - + string ScriptFolderPath { get; } IEnumerable ScriptFileTypes { get; } diff --git a/src/Umbraco.Core/Constants-Conventions.cs b/src/Umbraco.Core/Constants-Conventions.cs index a19e59f4ef..a56b40005e 100644 --- a/src/Umbraco.Core/Constants-Conventions.cs +++ b/src/Umbraco.Core/Constants-Conventions.cs @@ -110,6 +110,11 @@ namespace Umbraco.Core /// Property alias for the Media's file extension. /// public const string Extension = "umbracoExtension"; + + /// + /// The default height/width of an image file if the size can't be determined from the metadata + /// + public const int DefaultSize = 200; } /// @@ -354,4 +359,4 @@ namespace Umbraco.Core } } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/IO/MediaFileSystem.cs b/src/Umbraco.Core/IO/MediaFileSystem.cs index fc6490e8cd..37fcdeba70 100644 --- a/src/Umbraco.Core/IO/MediaFileSystem.cs +++ b/src/Umbraco.Core/IO/MediaFileSystem.cs @@ -17,15 +17,15 @@ using Umbraco.Core.Models; namespace Umbraco.Core.IO { - /// - /// A custom file system provider for media - /// - [FileSystemProvider("media")] - public class MediaFileSystem : FileSystemWrapper - { - private readonly IContentSection _contentConfig; + /// + /// A custom file system provider for media + /// + [FileSystemProvider("media")] + public class MediaFileSystem : FileSystemWrapper + { + private readonly IContentSection _contentConfig; private readonly UploadAutoFillProperties _uploadAutoFillProperties; - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly object _folderCounterLock = new object(); private long _folderCounter; @@ -39,8 +39,8 @@ namespace Umbraco.Core.IO }; public MediaFileSystem(IFileSystem wrapped) - : this(wrapped, UmbracoConfig.For.UmbracoSettings().Content, ApplicationContext.Current.ProfilingLogger.Logger) - { } + : this(wrapped, UmbracoConfig.For.UmbracoSettings().Content, ApplicationContext.Current.ProfilingLogger.Logger) + { } public MediaFileSystem(IFileSystem wrapped, IContentSection contentConfig, ILogger logger) : base(wrapped) @@ -60,13 +60,13 @@ namespace Umbraco.Core.IO [Obsolete("This low-level method should NOT exist.")] public string GetRelativePath(int propertyId, string fileName) - { + { var sep = _contentConfig.UploadAllowDirectories - ? Path.DirectorySeparatorChar - : '-'; + ? Path.DirectorySeparatorChar + : '-'; - return propertyId.ToString(CultureInfo.InvariantCulture) + sep + fileName; - } + return propertyId.ToString(CultureInfo.InvariantCulture) + sep + fileName; + } [Obsolete("This low-level method should NOT exist.", false)] public string GetRelativePath(string subfolder, string fileName) @@ -264,7 +264,7 @@ namespace Umbraco.Core.IO var filename = Path.GetFileName(sourcepath); var filepath = GetMediaPath(filename, content.Key, propertyType.Key); this.CopyFile(sourcepath, filepath); - + return filepath; } @@ -321,7 +321,7 @@ namespace Umbraco.Core.IO /// /// private void SetUploadFile(IContentBase content, Property property, string filepath, Stream filestream) - { + { // will use filepath for extension, and filestream for length _uploadAutoFillProperties.Populate(content, property.Alias, filepath, filestream); } @@ -368,19 +368,20 @@ namespace Umbraco.Core.IO return new Size(width, height); } } + + //we have no choice but to try to read in via GDI + using (var image = Image.FromStream(stream)) + { + + var fileWidth = image.Width; + var fileHeight = image.Height; + return new Size(fileWidth, fileHeight); + } } catch (Exception) { //We will just swallow, just means we can't read exif data, we don't want to log an error either - } - - //we have no choice but to try to read in via GDI - using (var image = Image.FromStream(stream)) - { - - var fileWidth = image.Width; - var fileHeight = image.Height; - return new Size(fileWidth, fileHeight); + return new Size(Constants.Conventions.Media.DefaultSize, Constants.Conventions.Media.DefaultSize); } } @@ -430,8 +431,8 @@ namespace Umbraco.Core.IO } } - public void DeleteMediaFiles(IEnumerable files) - { + public void DeleteMediaFiles(IEnumerable files) + { files = files.Distinct(); Parallel.ForEach(files, file => diff --git a/src/Umbraco.Core/Media/Exif/ImageFile.cs b/src/Umbraco.Core/Media/Exif/ImageFile.cs index 66350338e9..05a2a955c4 100644 --- a/src/Umbraco.Core/Media/Exif/ImageFile.cs +++ b/src/Umbraco.Core/Media/Exif/ImageFile.cs @@ -2,6 +2,7 @@ using System.Drawing; using System.IO; using System.Text; +using Umbraco.Core.Media.TypeDetector; namespace Umbraco.Core.Media.Exif { @@ -118,22 +119,25 @@ namespace Umbraco.Core.Media.Exif /// The created from the file. public static ImageFile FromStream(Stream stream, Encoding encoding) { - stream.Seek (0, SeekOrigin.Begin); - byte[] header = new byte[8]; - stream.Seek (0, SeekOrigin.Begin); - if (stream.Read (header, 0, header.Length) != header.Length) - throw new NotValidImageFileException (); - - // JPEG - if (header[0] == 0xFF && header[1] == 0xD8) - return new JPEGFile (stream, encoding); - - // TIFF - string tiffHeader = Encoding.ASCII.GetString (header, 0, 4); - if (tiffHeader == "MM\x00\x2a" || tiffHeader == "II\x2a\x00") - return new TIFFFile (stream, encoding); - - throw new NotValidImageFileException (); + // JPEG + if(JPEGDetector.IsOfType(stream)) + { + return new JPEGFile(stream, encoding); + } + + // TIFF + if (TIFFDetector.IsOfType(stream)) + { + return new TIFFFile(stream, encoding); + } + + // SVG + if (SVGDetector.IsOfType(stream)) + { + return new SVGFile(stream); + } + + throw new NotValidImageFileException (); } #endregion } diff --git a/src/Umbraco.Core/Media/Exif/ImageFileFormat.cs b/src/Umbraco.Core/Media/Exif/ImageFileFormat.cs index 8e1433c947..964b33e6ea 100644 --- a/src/Umbraco.Core/Media/Exif/ImageFileFormat.cs +++ b/src/Umbraco.Core/Media/Exif/ImageFileFormat.cs @@ -17,5 +17,9 @@ /// The file is a TIFF File. /// TIFF, + /// + /// The file is a SVG File. + /// + SVG, } } diff --git a/src/Umbraco.Core/Media/Exif/SVGFile.cs b/src/Umbraco.Core/Media/Exif/SVGFile.cs new file mode 100644 index 0000000000..bc4d94749c --- /dev/null +++ b/src/Umbraco.Core/Media/Exif/SVGFile.cs @@ -0,0 +1,37 @@ +using System; +using System.Drawing; +using System.IO; +using System.Linq; +using System.Xml.Linq; + +namespace Umbraco.Core.Media.Exif +{ + internal class SVGFile : ImageFile + { + public SVGFile(Stream fileStream) + { + fileStream.Position = 0; + + var document = XDocument.Load(fileStream); //if it throws an exception the ugly try catch in MediaFileSystem will catch it + + var width = document.Root?.Attributes().Where(x => x.Name == "width").Select(x => x.Value).FirstOrDefault(); + var height = document.Root?.Attributes().Where(x => x.Name == "height").Select(x => x.Value).FirstOrDefault(); + + Properties.Add(new ExifSInt(ExifTag.PixelYDimension, + height == null ? Constants.Conventions.Media.DefaultSize : int.Parse(height))); + Properties.Add(new ExifSInt(ExifTag.PixelXDimension, + width == null ? Constants.Conventions.Media.DefaultSize : int.Parse(width))); + + Format = ImageFileFormat.SVG; + } + + public override void Save(Stream stream) + { + } + + public override Image ToImage() + { + throw new NotImplementedException(); + } + } +} diff --git a/src/Umbraco.Core/Media/TypeDetector/JpegDetector.cs b/src/Umbraco.Core/Media/TypeDetector/JpegDetector.cs new file mode 100644 index 0000000000..5b68f6ad88 --- /dev/null +++ b/src/Umbraco.Core/Media/TypeDetector/JpegDetector.cs @@ -0,0 +1,14 @@ +using System.IO; + +namespace Umbraco.Core.Media.TypeDetector +{ + public class JPEGDetector : RasterizedTypeDetector + { + public static bool IsOfType(Stream fileStream) + { + var header = GetFileHeader(fileStream); + + return header[0] == 0xff && header[1] == 0xD8; + } + } +} diff --git a/src/Umbraco.Core/Media/TypeDetector/RasterizedTypeDetector.cs b/src/Umbraco.Core/Media/TypeDetector/RasterizedTypeDetector.cs new file mode 100644 index 0000000000..9777d55c20 --- /dev/null +++ b/src/Umbraco.Core/Media/TypeDetector/RasterizedTypeDetector.cs @@ -0,0 +1,16 @@ +using System.IO; + +namespace Umbraco.Core.Media.TypeDetector +{ + public abstract class RasterizedTypeDetector + { + public static byte[] GetFileHeader(Stream fileStream) + { + fileStream.Seek(0, SeekOrigin.Begin); + byte[] header = new byte[8]; + fileStream.Seek(0, SeekOrigin.Begin); + + return header; + } + } +} diff --git a/src/Umbraco.Core/Media/TypeDetector/SVGDetector.cs b/src/Umbraco.Core/Media/TypeDetector/SVGDetector.cs new file mode 100644 index 0000000000..ab9659ca1e --- /dev/null +++ b/src/Umbraco.Core/Media/TypeDetector/SVGDetector.cs @@ -0,0 +1,24 @@ +using System.IO; +using System.Xml.Linq; + +namespace Umbraco.Core.Media.TypeDetector +{ + public class SVGDetector + { + public static bool IsOfType(Stream fileStream) + { + var document = new XDocument(); + + try + { + document = XDocument.Load(fileStream); + } + catch (System.Exception ex) + { + return false; + } + + return document.Root?.Name.LocalName == "svg"; + } + } +} diff --git a/src/Umbraco.Core/Media/TypeDetector/TIFFDetector.cs b/src/Umbraco.Core/Media/TypeDetector/TIFFDetector.cs new file mode 100644 index 0000000000..2a6e42d0e0 --- /dev/null +++ b/src/Umbraco.Core/Media/TypeDetector/TIFFDetector.cs @@ -0,0 +1,24 @@ +using System.IO; +using System.Text; + +namespace Umbraco.Core.Media.TypeDetector +{ + public class TIFFDetector + { + public static bool IsOfType(Stream fileStream) + { + string tiffHeader = GetFileHeader(fileStream); + + return tiffHeader == "MM\x00\x2a" || tiffHeader == "II\x2a\x00"; + } + + public static string GetFileHeader(Stream fileStream) + { + var header = RasterizedTypeDetector.GetFileHeader(fileStream); + + string tiffHeader = Encoding.ASCII.GetString(header, 0, 4); + + return tiffHeader; + } + } +} diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index aa3b2db784..c7393e524f 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -365,6 +365,11 @@ + + + + + diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/imaging/umbimagegravity.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/imaging/umbimagegravity.directive.js index e47032fed3..a7f053fcae 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/imaging/umbimagegravity.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/imaging/umbimagegravity.directive.js @@ -31,10 +31,10 @@ angular.module("umbraco.directives") //elements var $viewport = element.find(".viewport"); var $image = element.find("img"); - var $overlay = element.find(".overlay"); + var $overlay = element.find(".overlay"); - scope.style = function () { - if(scope.dimensions.width <= 0){ + scope.style = function () { + if (scope.dimensions.width <= 0) { setDimensions(); } @@ -45,23 +45,29 @@ angular.module("umbraco.directives") }; scope.setFocalPoint = function(event) { + scope.$emit("imageFocalPointStart"); - scope.$emit("imageFocalPointStart"); + var offsetX = event.offsetX - 10; + var offsetY = event.offsetY - 10; - var offsetX = event.offsetX - 10; - var offsetY = event.offsetY - 10; - - calculateGravity(offsetX, offsetY); - - lazyEndEvent(); + calculateGravity(offsetX, offsetY); + lazyEndEvent(); }; - var setDimensions = function(){ - scope.dimensions.width = $image.width(); - scope.dimensions.height = $image.height(); - - if(scope.center){ + var setDimensions = function () { + if (scope.src.endsWith(".svg")) { + // svg files don't automatically get a size by + // loading them set a default size for now + $image.attr("width", "200"); + $image.attr("height", "200"); + // can't crop an svg file, don't show the focal point + $overlay.remove(); + } + scope.dimensions.width = $image.width(); + scope.dimensions.height = $image.height(); + + if(scope.center){ scope.dimensions.left = scope.center.left * scope.dimensions.width -10; scope.dimensions.top = scope.center.top * scope.dimensions.height -10; }else{ diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/imagecropper/imagecropper.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/imagecropper/imagecropper.html index e1c0118497..5c6f156aab 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/imagecropper/imagecropper.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/imagecropper/imagecropper.html @@ -46,8 +46,8 @@ Remove file - -
    + +
    • - jpeg,jpg,gif,bmp,png,tiff,tif + jpeg,jpg,gif,bmp,png,tiff,tif,svg src,alt,border,class,style,align,id,name,onclick,usemap @@ -105,7 +105,7 @@ throw - ashx,aspx,ascx,config,cshtml,vbhtml,asmx,air,axd,swf,xml,xhtml,html,htm,svg,php,htaccess + ashx,aspx,ascx,config,cshtml,vbhtml,asmx,air,axd,swf,xml,xhtml,html,htm,php,htaccess diff --git a/src/Umbraco.Web/Editors/MediaController.cs b/src/Umbraco.Web/Editors/MediaController.cs index 86e35ccc73..a7bc3c2707 100644 --- a/src/Umbraco.Web/Editors/MediaController.cs +++ b/src/Umbraco.Web/Editors/MediaController.cs @@ -942,4 +942,4 @@ namespace Umbraco.Web.Editors return hasPathAccess; } } -} +} \ No newline at end of file diff --git a/src/Umbraco.Web/PropertyEditors/FileUploadPropertyValueEditor.cs b/src/Umbraco.Web/PropertyEditors/FileUploadPropertyValueEditor.cs index 777a14b768..72ca5c5c59 100644 --- a/src/Umbraco.Web/PropertyEditors/FileUploadPropertyValueEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/FileUploadPropertyValueEditor.cs @@ -110,7 +110,7 @@ namespace Umbraco.Web.PropertyEditors _mediaFileSystem.AddFile(filepath, filestream, true); // must overwrite! var ext = _mediaFileSystem.GetExtension(filepath); - if (_mediaFileSystem.IsImageFile(ext)) + if (_mediaFileSystem.IsImageFile(ext) && ext != ".svg") { var preValues = editorValue.PreValues.FormatAsDictionary(); var sizes = preValues.Any() ? preValues.First().Value.Value : string.Empty; @@ -137,4 +137,4 @@ namespace Umbraco.Web.PropertyEditors return string.Join(",", newPaths.Select(x => _mediaFileSystem.GetUrl(x))); } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/PropertyEditors/ImageCropperPropertyValueEditor.cs b/src/Umbraco.Web/PropertyEditors/ImageCropperPropertyValueEditor.cs index 0e6500f3d8..ccee138486 100644 --- a/src/Umbraco.Web/PropertyEditors/ImageCropperPropertyValueEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/ImageCropperPropertyValueEditor.cs @@ -154,7 +154,7 @@ namespace Umbraco.Web.PropertyEditors _mediaFileSystem.AddFile(filepath, filestream, true); // must overwrite! var ext = _mediaFileSystem.GetExtension(filepath); - if (_mediaFileSystem.IsImageFile(ext)) + if (_mediaFileSystem.IsImageFile(ext) && ext != ".svg") { var preValues = editorValue.PreValues.FormatAsDictionary(); var sizes = preValues.Any() ? preValues.First().Value.Value : string.Empty; diff --git a/src/umbraco.businesslogic/IO/IOHelper.cs b/src/umbraco.businesslogic/IO/IOHelper.cs index 8a7e10d9eb..295e0df82d 100644 --- a/src/umbraco.businesslogic/IO/IOHelper.cs +++ b/src/umbraco.businesslogic/IO/IOHelper.cs @@ -13,9 +13,9 @@ using umbraco.businesslogic.Exceptions; namespace umbraco.IO { - [Obsolete("Use Umbraco.Core.IO.IOHelper instead")] + [Obsolete("Use Umbraco.Core.IO.IOHelper instead")] public static class IOHelper - { + { public static char DirSepChar { get @@ -27,42 +27,42 @@ namespace umbraco.IO //helper to try and match the old path to a new virtual one public static string FindFile(string virtualPath) { - return Umbraco.Core.IO.IOHelper.FindFile(virtualPath); + return Umbraco.Core.IO.IOHelper.FindFile(virtualPath); } //Replaces tildes with the root dir public static string ResolveUrl(string virtualPath) { - return Umbraco.Core.IO.IOHelper.ResolveUrl(virtualPath); + return Umbraco.Core.IO.IOHelper.ResolveUrl(virtualPath); } - [Obsolete("Use Umbraco.Web.Templates.TemplateUtilities.ResolveUrlsFromTextString instead, this method on this class will be removed in future versions")] + [Obsolete("Use Umbraco.Web.Templates.TemplateUtilities.ResolveUrlsFromTextString instead, this method on this class will be removed in future versions")] public static string ResolveUrlsFromTextString(string text) { - return Umbraco.Core.IO.IOHelper.ResolveUrlsFromTextString(text); + return Umbraco.Core.IO.IOHelper.ResolveUrlsFromTextString(text); } public static string MapPath(string path, bool useHttpContext) { - return Umbraco.Core.IO.IOHelper.MapPath(path, useHttpContext); + return Umbraco.Core.IO.IOHelper.MapPath(path, useHttpContext); } public static string MapPath(string path) { - return Umbraco.Core.IO.IOHelper.MapPath(path); + return Umbraco.Core.IO.IOHelper.MapPath(path); } //use a tilde character instead of the complete path - [Obsolete("This method is no longer in use and will be removed in future versions")] + [Obsolete("This method is no longer in use and will be removed in future versions")] public static string returnPath(string settingsKey, string standardPath, bool useTilde) { - return Umbraco.Core.IO.IOHelper.ReturnPath(settingsKey, standardPath, useTilde); + return Umbraco.Core.IO.IOHelper.ReturnPath(settingsKey, standardPath, useTilde); } - [Obsolete("This method is no longer in use and will be removed in future versions")] + [Obsolete("This method is no longer in use and will be removed in future versions")] public static string returnPath(string settingsKey, string standardPath) { - return Umbraco.Core.IO.IOHelper.ReturnPath(settingsKey, standardPath); + return Umbraco.Core.IO.IOHelper.ReturnPath(settingsKey, standardPath); } @@ -75,12 +75,12 @@ namespace umbraco.IO /// true if valid, throws a FileSecurityException if not public static bool ValidateEditPath(string filePath, string validDir) { - return Umbraco.Core.IO.IOHelper.ValidateEditPath(filePath, validDir); + return Umbraco.Core.IO.IOHelper.ValidateEditPath(filePath, validDir); } - public static bool ValidateFileExtension(string filePath, List validFileExtensions) + public static bool ValidateFileExtension(string filePath, List validFileExtensions) { - return Umbraco.Core.IO.IOHelper.ValidateFileExtension(filePath, validFileExtensions); + return Umbraco.Core.IO.IOHelper.ValidateFileExtension(filePath, validFileExtensions); } @@ -92,7 +92,7 @@ namespace umbraco.IO /// private static string getRootDirectorySafe() { - return Umbraco.Core.IO.IOHelper.GetRootDirectorySafe(); + return Umbraco.Core.IO.IOHelper.GetRootDirectorySafe(); } }