Copied to clipboard

Flag this post as spam?

This post will be reported to the moderators as potential spam to be looked at


  • auroris 1 post 72 karma points
    Sep 23, 2020 @ 18:42
    auroris
    1

    Converting a base64 encoded image into a proper Umbraco media image

    For some reason I don't clearly understand, when an editor inserts a decent amount of base64 encoded images it destroys Umbraco when using the Grid layout. Pegs the CPU at 100% for minutes at a time. While this denial of service is completely unintentional, I didn't want editors accidentally inserting base64 encoded images.

    I have written a class that hooks the ContentService.Saving event and converts the data: urls into proper Umbraco media images, and I'd like to share what I've created with you.

    This class is meant to be placed into the App_Code folder (create one if you haven't got one), and then compiled along with the rest of your Umbraco project.

    You can find my gist here if you like syntax highlighting.

    using HtmlAgilityPack;
    using Newtonsoft.Json.Linq;
    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.Linq;
    using Umbraco.Core;
    using Umbraco.Core.Composing;
    using Umbraco.Core.Events;
    using Umbraco.Core.Models;
    using Umbraco.Core.Models.Entities;
    using Umbraco.Core.Services;
    using Umbraco.Core.Services.Implement;
    
    namespace Umbraco.App_Code
    {
         [RuntimeLevel(MinLevel = RuntimeLevel.Run)]
         public class Base64MediaConverterComposer : ComponentComposer<Base64MediaConverterComponent>
         { }
    
         public class Base64MediaConverterComponent : IComponent
         {
              public void Initialize()
              {
                    // Subscribe to Umbraco events
                    ContentService.Saving += ContentService_Saving;
              }
    
              private void ContentService_Saving(IContentService sender, ContentSavingEventArgs e)
              {
                    // Iterate through all values being saved
                    foreach (IContent savedEntity in e.SavedEntities)
                    {
                         foreach (Property prop in savedEntity.Properties)
                         {
                              foreach (Property.PropertyValue val in prop.Values)
                              {
                                    // If the value is a Grid editor
                                    if (prop.PropertyType.PropertyEditorAlias.Equals("Umbraco.Grid"))
                                    {
                                         // Load the JSON document
                                         JObject doc = JObject.Parse(val.EditedValue as String);
    
                                         // Find the "controls" elements which specifies subdocuments in a grid's row/column location
                                         List<JToken> controlGroup = doc.Descendants().Where(x => x is JObject && x["controls"] != null).ToList();
    
                                         // For each row/column location with at least one control
                                         foreach (var controls in controlGroup)
                                         {
                                              // For each control in a location
                                              foreach (var control in controls["controls"])
                                              {
                                                    // If the control is type "rte" then we do stuff; else, ignore 
                                                    // Other control types like "image" or "macro" are irrelevant for this purpose
                                                    // see config\grid.editors.config.js for control aliases
                                                    if (control["editor"]["alias"].ToString().Equals("rte"))
                                                    {
                                                         try
                                                         {
                                                              // Replace the sub document in the json
                                                              if ((String)control["value"] != null && ((String)control["value"]).IndexOf("data:", StringComparison.OrdinalIgnoreCase) > -1)
                                                              {
                                                                    control["value"] = ReplaceBase64((String)control["value"], savedEntity);
                                                              }
                                                         }
                                                         catch (Exception ex)
                                                         {
                                                              // Log the error, bubble up message to back office
                                                              Current.Logger.Error(this.GetType(), "Error converting base64 image: " + ex.ToString(), ex);
                                                              e.Cancel = true;
                                                              e.Messages.Add(new EventMessage("Error converting image", "An error occured when attempting to convert an embedded image into a media library image.", EventMessageType.Error));
                                                         }
                                                    }
                                              }
                                         }
    
                                         // Replace the JSON document
                                         prop.SetValue(doc.ToString(), val.Culture, val.Segment);
                                    }
    
                                    // If the value is a TinyMCE editor
                                    if (prop.PropertyType.PropertyEditorAlias.Equals("Umbraco.TinyMCE"))
                                    {
                                         try
                                         {
                                              if (val.EditedValue != null && ((String)val.EditedValue).IndexOf("data:", StringComparison.OrdinalIgnoreCase) > -1)
                                              {
                                                    // Replace the html document
                                                    prop.SetValue(ReplaceBase64(val.EditedValue as String, savedEntity), val.Culture, val.Segment);
                                              }
                                         }
                                         catch (Exception ex)
                                         {
                                              // Log the error, bubble up message to back office
                                              Current.Logger.Error(this.GetType(), "Error converting base64 image: " + ex.ToString(), ex);
                                              e.Cancel = true;
                                              e.Messages.Add(new EventMessage("Error converting image", "An error occured when attempting to convert an embedded image into a media library image.", EventMessageType.Error));
                                         }
                                    }
                              }
                         }
                    }
              }
    
              /// <summary>
              /// Saves a base64 encoded image into the image library and then replaces the img src with the location of the image
              /// </summary>
              /// <param name="content">An HTML document fragment</param>
              /// <param name="entity">The content entity the image belongs to</param>
              /// <returns>An updated HTML document fragment</returns>
              String ReplaceBase64(String content, IUmbracoEntity entity)
              {
                    if (content == null || entity == null) { return content; }
    
                    HtmlDocument doc = new HtmlDocument();
                    doc.LoadHtml(content);
    
                    // For every image
                    foreach (HtmlNode node in doc.DocumentNode.Descendants("img"))
                    {
                         // Is it a data url?
                         if (node.Attributes["src"] != null && node.Attributes["src"].Value.IndexOf("data:", StringComparison.OrdinalIgnoreCase) > -1)
                         {
                              // Convert the data url
                              String[] data = node.Attributes["src"].Value.Split(new[] { ':', ';', ',' });
                              String fileMime = data[1];
                              String fileName = Guid.NewGuid().ToString() + "." + fileMime.Split('/')[1];
                              byte[] fileBytes = Convert.FromBase64String(data[3]);
    
                              // Umbraco save image
                              IMedia image = Current.Services.MediaService.CreateMedia(fileName, Constants.System.Root, Constants.Conventions.MediaTypes.Image);
                              image.SetValue(Current.Services.ContentTypeBaseServices, Constants.Conventions.Media.File, fileName, new MemoryStream(fileBytes));
                              Current.Services.MediaService.Save(image);
    
                              // Tell Umbraco that the document uses the image
                              Current.Services.RelationService.Relate(entity, image, Constants.Conventions.RelationTypes.RelatedMediaAlias);
    
                              // Update img src
                              node.SetAttributeValue("src", image.GetUrl(Constants.Conventions.Media.File, Current.Logger));
                         }
                    }
    
                    // Return the html document fragment
                    return doc.DocumentNode.OuterHtml;
              }
    
              public void Terminate()
              {
                    //unsubscribe during shutdown
                    ContentService.Saving -= ContentService_Saving;
              }
         }
    }
    
Please Sign in or register to post replies

Write your reply to:

Draft