mirror of https://github.com/abpframework/abp.git
3 changed files with 169 additions and 30 deletions
@ -0,0 +1,8 @@ |
|||
using System.Threading.Tasks; |
|||
|
|||
namespace Volo.CmsKit.Web.Renderers; |
|||
|
|||
public interface IMarkdownToHtmlRenderer |
|||
{ |
|||
Task<string> RenderAsync(string rawMarkdown, bool preventXSS = true); |
|||
} |
|||
@ -0,0 +1,129 @@ |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Text.RegularExpressions; |
|||
using Markdig; |
|||
using System.Threading.Tasks; |
|||
using System.Web; |
|||
using Volo.Abp.DependencyInjection; |
|||
using Ganss.XSS; |
|||
|
|||
namespace Volo.CmsKit.Web.Renderers; |
|||
|
|||
public class MarkdownToHtmlRenderer : IMarkdownToHtmlRenderer, ITransientDependency |
|||
{ |
|||
private readonly HtmlSanitizer _htmlSanitizer; |
|||
protected MarkdownPipeline MarkdownPipeline { get; } |
|||
|
|||
public MarkdownToHtmlRenderer(MarkdownPipeline markdownPipeline) |
|||
{ |
|||
MarkdownPipeline = markdownPipeline; |
|||
_htmlSanitizer = new HtmlSanitizer(); |
|||
} |
|||
|
|||
public async Task<string> RenderAsync(string rawMarkdown, bool preventXSS = false) |
|||
{ |
|||
if (preventXSS) |
|||
{ |
|||
rawMarkdown = EncodeHtmlTags(rawMarkdown, true); |
|||
} |
|||
|
|||
var html = Markdown.ToHtml(rawMarkdown, MarkdownPipeline); |
|||
|
|||
if (preventXSS) |
|||
{ |
|||
html = _htmlSanitizer.Sanitize(html); |
|||
} |
|||
|
|||
return html; |
|||
} |
|||
|
|||
|
|||
private static List<CodeBlockIndexPair> GetCodeBlockIndices(string markdownText) |
|||
{ |
|||
var regexObj = new Regex(@"```(\w)*|`(\w)*", RegexOptions.IgnoreCase | |
|||
RegexOptions.IgnorePatternWhitespace | |
|||
RegexOptions.Singleline | |
|||
RegexOptions.Multiline | |
|||
RegexOptions.ExplicitCapture); |
|||
|
|||
var matches = regexObj.Matches(markdownText); |
|||
var indices = new List<CodeBlockIndexPair>(); |
|||
|
|||
for (var i = 0; i < matches.Count; i++) |
|||
{ |
|||
if (!indices.Any() || indices.Last().EndIndex.HasValue) |
|||
{ |
|||
indices.Add(new CodeBlockIndexPair(matches[i].Index)); |
|||
} |
|||
else |
|||
{ |
|||
indices.Last().EndIndex = matches[i].Index; |
|||
} |
|||
} |
|||
|
|||
return indices; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Encodes html tags.
|
|||
/// </summary>
|
|||
private static string EncodeHtmlTags(string text, bool dontEncodeCodeBlocks = true) |
|||
{ |
|||
List<CodeBlockIndexPair> codeBlockIndices = null; |
|||
if (dontEncodeCodeBlocks) |
|||
{ |
|||
codeBlockIndices = GetCodeBlockIndices(text); |
|||
} |
|||
|
|||
return Regex.Replace(text, @"<[^>]*>", match => |
|||
{ |
|||
if (dontEncodeCodeBlocks && codeBlockIndices != null) |
|||
{ |
|||
var isInCodeBlock = false; |
|||
foreach (var codeBlock in codeBlockIndices) |
|||
{ |
|||
if (IsInCodeBlock(match.Index, codeBlock.StartIndex, codeBlock.EndIndex)) |
|||
{ |
|||
isInCodeBlock = true; |
|||
break; |
|||
} |
|||
} |
|||
|
|||
if (isInCodeBlock) |
|||
{ |
|||
return match.ToString(); |
|||
} |
|||
else |
|||
{ |
|||
return HttpUtility.HtmlEncode(match.ToString()); |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
return HttpUtility.HtmlEncode(match.ToString()); |
|||
} |
|||
}); |
|||
} |
|||
|
|||
private static bool IsInCodeBlock(int currentIndex, int codeBlockStartIndex, int? codeBlockEndIndex) |
|||
{ |
|||
if (codeBlockEndIndex.HasValue) |
|||
{ |
|||
return (currentIndex >= codeBlockStartIndex && currentIndex <= codeBlockEndIndex); |
|||
} |
|||
|
|||
return currentIndex >= codeBlockStartIndex; |
|||
} |
|||
|
|||
private class CodeBlockIndexPair |
|||
{ |
|||
public int StartIndex { get; private set; } |
|||
public int? EndIndex { get; set; } |
|||
|
|||
public CodeBlockIndexPair(int startIndex, int? endIndex = null) |
|||
{ |
|||
StartIndex = startIndex; |
|||
EndIndex = endIndex; |
|||
} |
|||
} |
|||
} |
|||
@ -1,38 +1,40 @@ |
|||
<Project Sdk="Microsoft.NET.Sdk.Web"> |
|||
|
|||
<Import Project="..\..\..\..\common.props" /> |
|||
<Import Project="..\..\..\..\configureawait.props" /> |
|||
<Import Project="..\..\..\..\common.props" /> |
|||
<Import Project="..\..\..\..\configureawait.props" /> |
|||
|
|||
<PropertyGroup> |
|||
<TargetFramework>net6.0</TargetFramework> |
|||
<AssetTargetFallback>$(AssetTargetFallback);portable-net45+win8+wp8+wpa81;</AssetTargetFallback> |
|||
<IsPackable>true</IsPackable> |
|||
<OutputType>Library</OutputType> |
|||
<RootNamespace>Volo.CmsKit.Web</RootNamespace> |
|||
<GenerateEmbeddedFilesManifest>true</GenerateEmbeddedFilesManifest> |
|||
</PropertyGroup> |
|||
<PropertyGroup> |
|||
<TargetFramework>net6.0</TargetFramework> |
|||
<AssetTargetFallback>$(AssetTargetFallback);portable-net45+win8+wp8+wpa81;</AssetTargetFallback> |
|||
<IsPackable>true</IsPackable> |
|||
<OutputType>Library</OutputType> |
|||
<RootNamespace>Volo.CmsKit.Web</RootNamespace> |
|||
<GenerateEmbeddedFilesManifest>true</GenerateEmbeddedFilesManifest> |
|||
</PropertyGroup> |
|||
|
|||
<ItemGroup> |
|||
<ProjectReference Include="..\..\..\..\framework\src\Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared\Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.csproj" /> |
|||
<ProjectReference Include="..\..\..\..\framework\src\Volo.Abp.AutoMapper\Volo.Abp.AutoMapper.csproj" /> |
|||
<ProjectReference Include="..\Volo.CmsKit.Common.Application.Contracts\Volo.CmsKit.Common.Application.Contracts.csproj" /> |
|||
</ItemGroup> |
|||
<ItemGroup> |
|||
<ProjectReference Include="..\..\..\..\framework\src\Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared\Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.csproj" /> |
|||
<ProjectReference Include="..\..\..\..\framework\src\Volo.Abp.AutoMapper\Volo.Abp.AutoMapper.csproj" /> |
|||
<ProjectReference Include="..\Volo.CmsKit.Common.Application.Contracts\Volo.CmsKit.Common.Application.Contracts.csproj" /> |
|||
</ItemGroup> |
|||
|
|||
<ItemGroup> |
|||
<PackageReference Include="Microsoft.Extensions.FileProviders.Embedded" Version="6.0.5" /> |
|||
</ItemGroup> |
|||
<ItemGroup> |
|||
<PackageReference Include="Microsoft.Extensions.FileProviders.Embedded" Version="6.0.5" /> |
|||
<PackageReference Include="Markdig.Signed" Version="0.26.0" /> |
|||
<PackageReference Include="HtmlSanitizer" Version="5.0.331" /> |
|||
</ItemGroup> |
|||
|
|||
<ItemGroup> |
|||
<EmbeddedResource Include="wwwroot\**\*.*" /> |
|||
<Content Remove="wwwroot\**\*.*" /> |
|||
<EmbeddedResource Include="Pages\**\*.css" /> |
|||
<Content Remove="Pages\**\*.css" /> |
|||
<EmbeddedResource Include="Pages\**\*.js" /> |
|||
<Content Remove="Pages\**\*.js" /> |
|||
<EmbeddedResource Include="Components\**\*.js" /> |
|||
<EmbeddedResource Include="Components\**\*.css" /> |
|||
<Content Remove="Components\**\*.js" /> |
|||
<Content Remove="Components\**\*.css" /> |
|||
</ItemGroup> |
|||
<ItemGroup> |
|||
<EmbeddedResource Include="wwwroot\**\*.*" /> |
|||
<Content Remove="wwwroot\**\*.*" /> |
|||
<EmbeddedResource Include="Pages\**\*.css" /> |
|||
<Content Remove="Pages\**\*.css" /> |
|||
<EmbeddedResource Include="Pages\**\*.js" /> |
|||
<Content Remove="Pages\**\*.js" /> |
|||
<EmbeddedResource Include="Components\**\*.js" /> |
|||
<EmbeddedResource Include="Components\**\*.css" /> |
|||
<Content Remove="Components\**\*.js" /> |
|||
<Content Remove="Components\**\*.css" /> |
|||
</ItemGroup> |
|||
|
|||
</Project> |
|||
|
|||
Loading…
Reference in new issue