Merge remote-tracking branch 'origin/netcore/netcore' into feature/netcore/msdi-refactor-use-DefaultControllerActivator

# Conflicts:
#	src/Umbraco.Web.Website/Extensions/UmbracoWebstiteServiceCollectionExtensions.cs
#	src/Umbraco.Web.Website/Runtime/WebsiteComposer.cs
This commit is contained in:
Bjarke Berg
2020-11-16 10:35:51 +01:00
66 changed files with 1443 additions and 1502 deletions

View File

@@ -1,6 +1,7 @@
using System;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Html;
using Microsoft.AspNetCore.Http.Extensions;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.Razor;
@@ -8,12 +9,12 @@ using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Umbraco.Core;
using Umbraco.Core.Configuration;
using Umbraco.Core.Configuration.Models;
using Umbraco.Core.IO;
using Umbraco.Core.Logging;
using Umbraco.Core.Models.PublishedContent;
using Umbraco.Core.Strings;
using Umbraco.Extensions;
using Umbraco.Web.Common.ModelBinders;
namespace Umbraco.Web.Common.AspNetCore
@@ -110,6 +111,8 @@ namespace Umbraco.Web.Common.AspNetCore
ViewData = (ViewDataDictionary<TModel>) viewData;
}
// viewData is the ViewDataDictionary (maybe <TModel>) that we have
// modelType is the type of the model that we need to bind to
//
@@ -143,5 +146,16 @@ namespace Umbraco.Web.Common.AspNetCore
var nViewData = (ViewDataDictionary)Activator.CreateInstance(nViewDataType, tViewData);
return nViewData;
}
public HtmlString RenderSection(string name, HtmlString defaultContents)
{
return RazorPageExtensions.RenderSection(this, name, defaultContents);
}
public HtmlString RenderSection(string name, string defaultContents)
{
return RazorPageExtensions.RenderSection(this, name, defaultContents);
}
}
}

View File

@@ -8,5 +8,12 @@
internal const string ViewLocation = "~/Views";
internal const string DataTokenCurrentViewContext = "umbraco-current-view-context";
internal static class ReservedAdditionalKeys
{
internal const string Controller = "c";
internal const string Action = "a";
internal const string Area = "ar";
}
}
}

View File

@@ -0,0 +1,44 @@
using System;
using System.Runtime.Serialization;
namespace Umbraco.Web.Common.Exceptions
{
/// <summary>
/// Exception that occurs when an Umbraco form route string is invalid
/// </summary>
[Serializable]
public sealed class HttpUmbracoFormRouteStringException : Exception
{
/// <summary>
/// Initializes a new instance of the <see cref="HttpUmbracoFormRouteStringException" /> class.
/// </summary>
public HttpUmbracoFormRouteStringException()
{ }
/// <summary>
/// Initializes a new instance of the <see cref="HttpUmbracoFormRouteStringException" /> class.
/// </summary>
/// <param name="info">The <see cref="T:System.Runtime.Serialization.SerializationInfo" /> that holds the serialized object data about the exception being thrown.</param>
/// <param name="context">The <see cref="T:System.Runtime.Serialization.StreamingContext" /> that holds the contextual information about the source or destination.</param>
private HttpUmbracoFormRouteStringException(SerializationInfo info, StreamingContext context)
: base(info, context)
{ }
/// <summary>
/// Initializes a new instance of the <see cref="HttpUmbracoFormRouteStringException" /> class.
/// </summary>
/// <param name="message">The error message displayed to the client when the exception is thrown.</param>
public HttpUmbracoFormRouteStringException(string message)
: base(message)
{ }
/// <summary>
/// Initializes a new instance of the <see cref="HttpUmbracoFormRouteStringException" /> class.
/// </summary>
/// <param name="message">The error message displayed to the client when the exception is thrown.</param>
/// <param name="innerException">The <see cref="P:System.Exception.InnerException" />, if any, that threw the current exception.</param>
public HttpUmbracoFormRouteStringException(string message, Exception innerException)
: base(message, innerException)
{ }
}
}

View File

@@ -0,0 +1,38 @@
using System;
using Umbraco.Core.Models.Blocks;
using Umbraco.Core.Models.PublishedContent;
using Microsoft.AspNetCore.Html;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
namespace Umbraco.Extensions
{
public static class BlockListTemplateExtensions
{
public const string DefaultFolder = "BlockList/";
public const string DefaultTemplate = "Default";
public static IHtmlContent GetBlockListHtml(this HtmlHelper html, BlockListModel model, string template = DefaultTemplate)
{
if (model?.Count == 0) return new HtmlString(string.Empty);
var view = DefaultFolder + template;
return html.Partial(view, model);
}
public static IHtmlContent GetBlockListHtml(this HtmlHelper html, IPublishedProperty property, string template = DefaultTemplate) => GetBlockListHtml(html, property?.GetValue() as BlockListModel, template);
public static IHtmlContent GetBlockListHtml(this HtmlHelper html, IPublishedContent contentItem, string propertyAlias) => GetBlockListHtml(html, contentItem, propertyAlias, DefaultTemplate);
public static IHtmlContent GetBlockListHtml(this HtmlHelper html, IPublishedContent contentItem, string propertyAlias, string template)
{
if (propertyAlias == null) throw new ArgumentNullException(nameof(propertyAlias));
if (string.IsNullOrWhiteSpace(propertyAlias)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(propertyAlias));
var prop = contentItem.GetProperty(propertyAlias);
if (prop == null) throw new InvalidOperationException("No property type found with alias " + propertyAlias);
return GetBlockListHtml(html, prop?.GetValue() as BlockListModel, template);
}
}
}

View File

@@ -0,0 +1,52 @@
using System;
using Microsoft.AspNetCore.Html;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Umbraco.Core.Cache;
using Umbraco.Core.Hosting;
namespace Umbraco.Extensions
{
/// <summary>
/// Extension methods for the cache helper
/// </summary>
public static class CacheHelperExtensions
{
/// <summary>
/// Outputs and caches a partial view in MVC
/// </summary>
/// <param name="appCaches"></param>
/// <param name="hostingEnvironment"></param>
/// <param name="htmlHelper"></param>
/// <param name="partialViewName"></param>
/// <param name="model"></param>
/// <param name="cachedSeconds"></param>
/// <param name="cacheKey">used to cache the partial view, this key could change if it is cached by page or by member</param>
/// <param name="viewData"></param>
/// <returns></returns>
public static IHtmlContent CachedPartialView(
this AppCaches appCaches,
IHostingEnvironment hostingEnvironment,
IHtmlHelper htmlHelper,
string partialViewName,
object model,
int cachedSeconds,
string cacheKey,
ViewDataDictionary viewData = null)
{
//disable cached partials in debug mode: http://issues.umbraco.org/issue/U4-5940
if (hostingEnvironment.IsDebugMode)
{
// just return a normal partial view instead
return htmlHelper.Partial(partialViewName, model, viewData);
}
return appCaches.RuntimeCache.GetCacheItem<IHtmlContent>(
Core.CacheHelperExtensions.PartialViewCacheKey + cacheKey,
() => htmlHelper.Partial(partialViewName, model, viewData),
timeout: new TimeSpan(0, 0, 0, cachedSeconds));
}
}
}

View File

@@ -0,0 +1,19 @@
using Microsoft.AspNetCore.Html;
using Microsoft.AspNetCore.Mvc.Razor;
namespace Umbraco.Extensions
{
public static class RazorPageExtensions
{
public static HtmlString RenderSection(this RazorPage webPage, string name, HtmlString defaultContents)
{
return webPage.IsSectionDefined(name) ? webPage.RenderSection(name) : defaultContents;
}
public static HtmlString RenderSection(this RazorPage webPage, string name, string defaultContents)
{
return webPage.IsSectionDefined(name) ? webPage.RenderSection(name) : new HtmlString(defaultContents);
}
}
}

View File

@@ -0,0 +1,75 @@
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
namespace Umbraco.Extensions
{
public static class ViewContextExtensions
{
/// <summary>
/// Creates a new ViewContext from an existing one but specifies a new Model for the ViewData
/// </summary>
/// <param name="vc"></param>
/// <param name="model"></param>
/// <returns></returns>
public static ViewContext CopyWithModel(this ViewContext vc, object model)
{
return new ViewContext
{
View = vc.View,
Writer = vc.Writer,
ActionDescriptor = vc.ActionDescriptor,
FormContext = vc.FormContext,
HttpContext = vc.HttpContext,
RouteData = vc.RouteData,
TempData = vc.TempData,
ViewData = new ViewDataDictionary(vc.ViewData)
{
Model = model
},
ClientValidationEnabled = vc.ClientValidationEnabled,
ExecutingFilePath = vc.ExecutingFilePath,
ValidationMessageElement = vc.ValidationMessageElement,
Html5DateRenderingMode = vc.Html5DateRenderingMode,
ValidationSummaryMessageElement = vc.ValidationSummaryMessageElement
};
}
public static ViewContext Clone(this ViewContext vc)
{
return new ViewContext
{
View = vc.View,
Writer = vc.Writer,
ActionDescriptor = vc.ActionDescriptor,
FormContext = vc.FormContext,
HttpContext = vc.HttpContext,
RouteData = vc.RouteData,
TempData = vc.TempData,
ViewData = new ViewDataDictionary(vc.ViewData),
ClientValidationEnabled = vc.ClientValidationEnabled,
ExecutingFilePath = vc.ExecutingFilePath,
ValidationMessageElement = vc.ValidationMessageElement,
Html5DateRenderingMode = vc.Html5DateRenderingMode,
ValidationSummaryMessageElement = vc.ValidationSummaryMessageElement
};
}
//public static ViewContext CloneWithWriter(this ViewContext vc, TextWriter writer)
//{
// return new ViewContext
// {
// Controller = vc.Controller,
// HttpContext = vc.HttpContext,
// RequestContext = vc.RequestContext,
// RouteData = vc.RouteData,
// TempData = vc.TempData,
// View = vc.View,
// ViewData = vc.ViewData,
// FormContext = vc.FormContext,
// ClientValidationEnabled = vc.ClientValidationEnabled,
// UnobtrusiveJavaScriptEnabled = vc.UnobtrusiveJavaScriptEnabled,
// Writer = writer
// };
//}
}
}

View File

@@ -5,12 +5,12 @@ namespace Umbraco.Web.Common.Filters
/// <summary>
/// Ensures authorization is successful for a back office user.
/// </summary>
public class UmbracoAuthorizeAttribute : TypeFilterAttribute
public class UmbracoBackOfficeAuthorizeAttribute : TypeFilterAttribute
{
/// <summary>
/// Default constructor
/// </summary>
public UmbracoAuthorizeAttribute() : this(false, false)
public UmbracoBackOfficeAuthorizeAttribute() : this(false, false)
{
}
@@ -20,7 +20,7 @@ namespace Umbraco.Web.Common.Filters
/// <param name="redirectToUmbracoLogin"></param>
/// <param name="requireApproval"></param>
public UmbracoAuthorizeAttribute(bool redirectToUmbracoLogin, bool requireApproval) : base(typeof(UmbracoAuthorizeFilter))
public UmbracoBackOfficeAuthorizeAttribute(bool redirectToUmbracoLogin, bool requireApproval) : base(typeof(UmbracoBackOfficeAuthorizeFilter))
{
Arguments = new object[] { redirectToUmbracoLogin, requireApproval };
}
@@ -29,7 +29,7 @@ namespace Umbraco.Web.Common.Filters
/// Constructor with redirect url behavior
/// </summary>
/// <param name="redirectUrl"></param>
public UmbracoAuthorizeAttribute(string redirectUrl) : base(typeof(UmbracoAuthorizeFilter))
public UmbracoBackOfficeAuthorizeAttribute(string redirectUrl) : base(typeof(UmbracoBackOfficeAuthorizeFilter))
{
Arguments = new object[] { redirectUrl };
}

View File

@@ -13,7 +13,7 @@ namespace Umbraco.Web.Common.Filters
/// <summary>
/// Ensures authorization is successful for a back office user.
/// </summary>
public class UmbracoAuthorizeFilter : IAuthorizationFilter
public class UmbracoBackOfficeAuthorizeFilter : IAuthorizationFilter
{
private readonly bool _requireApproval;
@@ -28,7 +28,7 @@ namespace Umbraco.Web.Common.Filters
private readonly bool _redirectToUmbracoLogin;
private string _redirectUrl;
private UmbracoAuthorizeFilter(
private UmbracoBackOfficeAuthorizeFilter(
IHostingEnvironment hostingEnvironment,
IUmbracoContextAccessor umbracoContext,
IRuntimeState runtimeState, LinkGenerator linkGenerator,
@@ -51,7 +51,7 @@ namespace Umbraco.Web.Common.Filters
/// <param name="runtimeState"></param>
/// <param name="linkGenerator"></param>
/// <param name="redirectUrl"></param>
public UmbracoAuthorizeFilter(
public UmbracoBackOfficeAuthorizeFilter(
IHostingEnvironment hostingEnvironment,
IUmbracoContextAccessor umbracoContext,
IRuntimeState runtimeState, LinkGenerator linkGenerator,
@@ -59,7 +59,7 @@ namespace Umbraco.Web.Common.Filters
{
}
public UmbracoAuthorizeFilter(
public UmbracoBackOfficeAuthorizeFilter(
IHostingEnvironment hostingEnvironment,
IUmbracoContextAccessor umbracoContext,
IRuntimeState runtimeState, LinkGenerator linkGenerator,

View File

@@ -0,0 +1,20 @@
using Microsoft.AspNetCore.Mvc;
namespace Umbraco.Web.Common.Filters
{
/// <summary>
/// Ensures authorization is successful for a website user (member).
/// </summary>
public class UmbracoMemberAuthorizeAttribute : TypeFilterAttribute
{
public UmbracoMemberAuthorizeAttribute() : this(string.Empty, string.Empty, string.Empty)
{
}
public UmbracoMemberAuthorizeAttribute(string allowType, string allowGroup, string allowMembers) : base(typeof(UmbracoMemberAuthorizeFilter))
{
Arguments = new object[] { allowType, allowGroup, allowMembers};
}
}
}

View File

@@ -0,0 +1,69 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using System.Collections.Generic;
using Umbraco.Core;
using Umbraco.Extensions;
namespace Umbraco.Web.Common.Filters
{
/// <summary>
/// Ensures authorization is successful for a back office user.
/// </summary>
public class UmbracoMemberAuthorizeFilter : IAuthorizationFilter
{
/// <summary>
/// Comma delimited list of allowed member types
/// </summary>
public string AllowType { get; private set;}
/// <summary>
/// Comma delimited list of allowed member groups
/// </summary>
public string AllowGroup { get; private set;}
/// <summary>
/// Comma delimited list of allowed members
/// </summary>
public string AllowMembers { get; private set; }
private UmbracoMemberAuthorizeFilter(
string allowType, string allowGroup, string allowMembers)
{
AllowType = allowType;
AllowGroup = allowGroup;
AllowMembers = allowMembers;
}
public void OnAuthorization(AuthorizationFilterContext context)
{
if (!IsAuthorized())
{
context.HttpContext.SetReasonPhrase("Resource restricted: either member is not logged on or is not of a permitted type or group.");
context.Result = new ForbidResult();
}
}
private bool IsAuthorized()
{
if (AllowMembers.IsNullOrWhiteSpace())
AllowMembers = "";
if (AllowGroup.IsNullOrWhiteSpace())
AllowGroup = "";
if (AllowType.IsNullOrWhiteSpace())
AllowType = "";
var members = new List<int>();
foreach (var s in AllowMembers.Split(','))
{
if (int.TryParse(s, out var id))
{
members.Add(id);
}
}
return false;// TODO reintroduce when members are implemented: _memberHelper.IsMemberAuthorized(AllowType.Split(','), AllowGroup.Split(','), members);
}
}
}

View File

@@ -0,0 +1,74 @@
using System;
using Microsoft.AspNetCore.DataProtection;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.AspNetCore.Mvc.Filters;
using Umbraco.Core;
using Umbraco.Web.Common.Constants;
using Umbraco.Web.Common.Exceptions;
using Umbraco.Web.Common.Security;
namespace Umbraco.Web.Common.Filters
{
/// <summary>
/// Attribute used to check that the request contains a valid Umbraco form request string.
/// </summary>
/// <remarks>
/// Applying this attribute/filter to a <see cref="SurfaceController"/> or SurfaceController Action will ensure that the Action can only be executed
/// when it is routed to from within Umbraco, typically when rendering a form with BeginUmbracoForm. It will mean that the natural MVC route for this Action
/// will fail with a <see cref="HttpUmbracoFormRouteStringException"/>.
/// </remarks>
public class ValidateUmbracoFormRouteStringAttribute : TypeFilterAttribute
{
public ValidateUmbracoFormRouteStringAttribute() : base(typeof(ValidateUmbracoFormRouteStringFilter))
{
Arguments = new object[] { };
}
internal class ValidateUmbracoFormRouteStringFilter: IAuthorizationFilter
{
private readonly IDataProtectionProvider _dataProtectionProvider;
public ValidateUmbracoFormRouteStringFilter(IDataProtectionProvider dataProtectionProvider)
{
_dataProtectionProvider = dataProtectionProvider;
}
public void OnAuthorization(AuthorizationFilterContext context)
{
if (context == null) throw new ArgumentNullException(nameof(context));
var ufprt = context.HttpContext.Request.Form["ufprt"];
if (context.ActionDescriptor is ControllerActionDescriptor controllerActionDescriptor)
{
ValidateRouteString(ufprt, controllerActionDescriptor.ControllerName, controllerActionDescriptor.ActionName, context.RouteData?.DataTokens["area"]?.ToString());
}
}
public void ValidateRouteString(string ufprt, string currentController, string currentAction, string currentArea)
{
if (ufprt.IsNullOrWhiteSpace())
{
throw new HttpUmbracoFormRouteStringException("The required request field \"ufprt\" is not present.");
}
if (!EncryptionHelper.DecryptAndValidateEncryptedRouteString(_dataProtectionProvider, ufprt, out var additionalDataParts))
{
throw new HttpUmbracoFormRouteStringException("The Umbraco form request route string could not be decrypted.");
}
if (!additionalDataParts[ViewConstants.ReservedAdditionalKeys.Controller].InvariantEquals(currentController) ||
!additionalDataParts[ViewConstants.ReservedAdditionalKeys.Action].InvariantEquals(currentAction) ||
(!additionalDataParts[ViewConstants.ReservedAdditionalKeys.Area].IsNullOrWhiteSpace() && !additionalDataParts[ViewConstants.ReservedAdditionalKeys.Area].InvariantEquals(currentArea)))
{
throw new HttpUmbracoFormRouteStringException("The provided Umbraco form request route string was meant for a different controller and action.");
}
}
}
}
}

View File

@@ -0,0 +1,328 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Web;
using HtmlAgilityPack;
using Microsoft.AspNetCore.Html;
namespace Umbraco.Web.Common.Mvc
{
/// <summary>
/// Provides utility methods for UmbracoHelper for working with strings and HTML in views.
/// </summary>
public sealed class HtmlStringUtilities
{
/// <summary>
/// HTML encodes the text and replaces text line breaks with HTML line breaks.
/// </summary>
/// <param name="text">The text.</param>
/// <returns>
/// The HTML encoded text with text line breaks replaced with HTML line breaks (<c>&lt;br /&gt;</c>).
/// </returns>
public IHtmlContent ReplaceLineBreaks(string text)
{
var value = HttpUtility.HtmlEncode(text)?
.Replace("\r\n", "<br />")
.Replace("\r", "<br />")
.Replace("\n", "<br />");
return new HtmlString(value);
}
public HtmlString StripHtmlTags(string html, params string[] tags)
{
var doc = new HtmlDocument();
doc.LoadHtml("<p>" + html + "</p>");
var targets = new List<HtmlNode>();
var nodes = doc.DocumentNode.FirstChild.SelectNodes(".//*");
if (nodes != null)
{
foreach (var node in nodes)
{
//is element
if (node.NodeType != HtmlNodeType.Element) continue;
var filterAllTags = (tags == null || !tags.Any());
if (filterAllTags || tags.Any(tag => string.Equals(tag, node.Name, StringComparison.CurrentCultureIgnoreCase)))
{
targets.Add(node);
}
}
foreach (var target in targets)
{
HtmlNode content = doc.CreateTextNode(target.InnerText);
target.ParentNode.ReplaceChild(content, target);
}
}
else
{
return new HtmlString(html);
}
return new HtmlString(doc.DocumentNode.FirstChild.InnerHtml.Replace(" ", " "));
}
public string Join(string separator, params object[] args)
{
var results = args
.Where(x => x != null)
.Select(x => x.ToString())
.Where(x => string.IsNullOrWhiteSpace(x) == false);
return string.Join(separator, results);
}
public string Concatenate(params object[] args)
{
var sb = new StringBuilder();
foreach (var arg in args
.Where(x => x != null)
.Select(x => x.ToString())
.Where(x => string.IsNullOrWhiteSpace(x) == false))
{
sb.Append(arg);
}
return sb.ToString();
}
public string Coalesce(params object[] args)
{
var arg = args
.Where(x => x != null)
.Select(x => x.ToString())
.FirstOrDefault(x => string.IsNullOrWhiteSpace(x) == false);
return arg ?? string.Empty;
}
public IHtmlContent Truncate(string html, int length, bool addElipsis, bool treatTagsAsContent)
{
const string hellip = "&hellip;";
using (var outputms = new MemoryStream())
{
bool lengthReached = false;
using (var outputtw = new StreamWriter(outputms))
{
using (var ms = new MemoryStream())
{
using (var tw = new StreamWriter(ms))
{
tw.Write(html);
tw.Flush();
ms.Position = 0;
var tagStack = new Stack<string>();
using (TextReader tr = new StreamReader(ms))
{
bool isInsideElement = false,
insideTagSpaceEncountered = false,
isTagClose = false;
int ic = 0,
//currentLength = 0,
currentTextLength = 0;
string currentTag = string.Empty,
tagContents = string.Empty;
while ((ic = tr.Read()) != -1)
{
bool write = true;
switch ((char)ic)
{
case '<':
if (!lengthReached)
{
isInsideElement = true;
}
insideTagSpaceEncountered = false;
currentTag = string.Empty;
tagContents = string.Empty;
isTagClose = false;
if (tr.Peek() == (int)'/')
{
isTagClose = true;
}
break;
case '>':
isInsideElement = false;
if (isTagClose && tagStack.Count > 0)
{
string thisTag = tagStack.Pop();
outputtw.Write("</" + thisTag + ">");
if (treatTagsAsContent)
{
currentTextLength++;
}
}
if (!isTagClose && currentTag.Length > 0)
{
if (!lengthReached)
{
tagStack.Push(currentTag);
outputtw.Write("<" + currentTag);
if (treatTagsAsContent)
{
currentTextLength++;
}
if (!string.IsNullOrEmpty(tagContents))
{
if (tagContents.EndsWith("/"))
{
// No end tag e.g. <br />.
tagStack.Pop();
}
outputtw.Write(tagContents);
write = true;
insideTagSpaceEncountered = false;
}
outputtw.Write(">");
}
}
// Continue to next iteration of the text reader.
continue;
default:
if (isInsideElement)
{
if (ic == (int)' ')
{
if (!insideTagSpaceEncountered)
{
insideTagSpaceEncountered = true;
}
}
if (!insideTagSpaceEncountered)
{
currentTag += (char)ic;
}
}
break;
}
if (isInsideElement || insideTagSpaceEncountered)
{
write = false;
if (insideTagSpaceEncountered)
{
tagContents += (char)ic;
}
}
if (!isInsideElement || treatTagsAsContent)
{
currentTextLength++;
}
if (currentTextLength <= length || (lengthReached && isInsideElement))
{
if (write)
{
var charToWrite = (char)ic;
outputtw.Write(charToWrite);
//currentLength++;
}
}
if (!lengthReached)
{
if (currentTextLength == length)
{
// if the last character added was the first of a two character unicode pair, add the second character
if (char.IsHighSurrogate((char)ic))
{
var lowSurrogate = tr.Read();
outputtw.Write((char)lowSurrogate);
}
}
// only add elipsis if current length greater than original length
if (currentTextLength > length)
{
if (addElipsis)
{
outputtw.Write(hellip);
}
lengthReached = true;
}
}
}
}
}
}
outputtw.Flush();
outputms.Position = 0;
using (TextReader outputtr = new StreamReader(outputms))
{
string result = string.Empty;
string firstTrim = outputtr.ReadToEnd().Replace(" ", " ").Trim();
// Check to see if there is an empty char between the hellip and the output string
// if there is, remove it
if (addElipsis && lengthReached && string.IsNullOrWhiteSpace(firstTrim) == false)
{
result = firstTrim[firstTrim.Length - hellip.Length - 1] == ' ' ? firstTrim.Remove(firstTrim.Length - hellip.Length - 1, 1) : firstTrim;
}
else
{
result = firstTrim;
}
return new HtmlString(result);
}
}
}
}
/// <summary>
/// Returns the length of the words from a HTML block
/// </summary>
/// <param name="html">HTML text</param>
/// <param name="words">Amount of words you would like to measure</param>
/// <param name="tagsAsContent"></param>
/// <returns></returns>
public int WordsToLength(string html, int words)
{
HtmlDocument doc = new HtmlDocument();
doc.LoadHtml(html);
int wordCount = 0,
length = 0,
maxWords = words;
html = StripHtmlTags(html, null).ToString();
while (length < html.Length)
{
// Check to see if the current wordCount reached the maxWords allowed
if (wordCount.Equals(maxWords)) break;
// Check if current char is part of a word
while (length < html.Length && char.IsWhiteSpace(html[length]) == false)
{
length++;
}
wordCount++;
// Skip whitespace until the next word
while (length < html.Length && char.IsWhiteSpace(html[length]) && wordCount.Equals(maxWords) == false)
{
length++;
}
}
return length;
}
}
}

View File

@@ -0,0 +1,104 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Web;
using Microsoft.AspNetCore.DataProtection;
using Microsoft.Extensions.Logging;
using Umbraco.Core;
using Umbraco.Web.Common.Constants;
namespace Umbraco.Web.Common.Security
{
public class EncryptionHelper
{
private static IDataProtector CreateDataProtector(IDataProtectionProvider dataProtectionProvider)
{
return dataProtectionProvider.CreateProtector(nameof(EncryptionHelper));
}
public static string Decrypt(string encryptedString, IDataProtectionProvider dataProtectionProvider)
{
return CreateDataProtector(dataProtectionProvider).Unprotect(encryptedString);
}
public static string Encrypt(string plainString, IDataProtectionProvider dataProtectionProvider)
{
return CreateDataProtector(dataProtectionProvider).Protect(plainString);
}
/// <summary>
/// This is used in methods like BeginUmbracoForm and SurfaceAction to generate an encrypted string which gets submitted in a request for which
/// Umbraco can decrypt during the routing process in order to delegate the request to a specific MVC Controller.
/// </summary>
/// <param name="dataProtectionProvider"></param>
/// <param name="controllerName"></param>
/// <param name="controllerAction"></param>
/// <param name="area"></param>
/// <param name="additionalRouteVals"></param>
/// <returns></returns>
public static string CreateEncryptedRouteString(IDataProtectionProvider dataProtectionProvider, string controllerName, string controllerAction, string area, object additionalRouteVals = null)
{
if (dataProtectionProvider == null) throw new ArgumentNullException(nameof(dataProtectionProvider));
if (controllerName == null) throw new ArgumentNullException(nameof(controllerName));
if (string.IsNullOrEmpty(controllerName)) throw new ArgumentException("Value can't be empty.", nameof(controllerName));
if (controllerAction == null) throw new ArgumentNullException(nameof(controllerAction));
if (string.IsNullOrEmpty(controllerAction)) throw new ArgumentException("Value can't be empty.", nameof(controllerAction));
if (area == null) throw new ArgumentNullException(nameof(area));
//need to create a params string as Base64 to put into our hidden field to use during the routes
var surfaceRouteParams = $"{ViewConstants.ReservedAdditionalKeys.Controller}={WebUtility.UrlEncode(controllerName)}&{ViewConstants.ReservedAdditionalKeys.Action}={WebUtility.UrlEncode(controllerAction)}&{ViewConstants.ReservedAdditionalKeys.Area}={area}";
//checking if the additional route values is already a dictionary and convert to querystring
string additionalRouteValsAsQuery;
if (additionalRouteVals != null)
{
if (additionalRouteVals is Dictionary<string, object> additionalRouteValsAsDictionary)
additionalRouteValsAsQuery = additionalRouteValsAsDictionary.ToQueryString();
else
additionalRouteValsAsQuery = additionalRouteVals.ToDictionary<object>().ToQueryString();
}
else
additionalRouteValsAsQuery = null;
if (additionalRouteValsAsQuery.IsNullOrWhiteSpace() == false)
surfaceRouteParams += "&" + additionalRouteValsAsQuery;
return Encrypt(surfaceRouteParams, dataProtectionProvider);
}
public static bool DecryptAndValidateEncryptedRouteString(IDataProtectionProvider dataProtectionProvider, string encryptedString, out IDictionary<string, string> parts)
{
if (dataProtectionProvider == null) throw new ArgumentNullException(nameof(dataProtectionProvider));
string decryptedString;
try
{
decryptedString = Decrypt(encryptedString, dataProtectionProvider);
}
catch (Exception)
{
StaticApplicationLogging.Logger.LogWarning("A value was detected in the ufprt parameter but Umbraco could not decrypt the string");
parts = null;
return false;
}
var parsedQueryString = HttpUtility.ParseQueryString(decryptedString);
parts = new Dictionary<string, string>();
foreach (var key in parsedQueryString.AllKeys)
{
parts[key] = parsedQueryString[key];
}
//validate all required keys exist
//the controller
if (parts.All(x => x.Key != ViewConstants.ReservedAdditionalKeys.Controller))
return false;
//the action
if (parts.All(x => x.Key != ViewConstants.ReservedAdditionalKeys.Action))
return false;
//the area
if (parts.All(x => x.Key != ViewConstants.ReservedAdditionalKeys.Area))
return false;
return true;
}
}
}