diff --git a/modules/cms-kit/src/Volo.CmsKit.Public.Application/Volo/CmsKit/Public/Comments/CommentPublicAppService.cs b/modules/cms-kit/src/Volo.CmsKit.Public.Application/Volo/CmsKit/Public/Comments/CommentPublicAppService.cs index 4a7bd8be8c..8d378ad925 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Public.Application/Volo/CmsKit/Public/Comments/CommentPublicAppService.cs +++ b/modules/cms-kit/src/Volo.CmsKit.Public.Application/Volo/CmsKit/Public/Comments/CommentPublicAppService.cs @@ -25,7 +25,8 @@ namespace Volo.CmsKit.Public.Comments; [RequiresGlobalFeature(typeof(CommentsFeature))] public class CommentPublicAppService : CmsKitPublicAppServiceBase, ICommentPublicAppService { - protected string RegexMarkdownUrlPattern = @"\[[^\]]*\]\((?.*?)\)(?![^\x60]*\x60)"; + protected string RegexUrlPattern = + @"https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()!@:%_\+.~#?&\/\/=]*)"; protected ICommentRepository CommentRepository { get; } protected ICmsUserLookupService CmsUserLookupService { get; } @@ -124,7 +125,41 @@ public class CommentPublicAppService : CmsKitPublicAppServiceBase, ICommentPubli throw new AbpAuthorizationException(); } } + + protected virtual void CheckExternalUrls(string entityType, string text) + { + if (!CmsCommentOptions.AllowedExternalUrls.TryGetValue(entityType, out var allowedExternalUrls)) + { + return; + } + + text = text.Replace("www.", "https://www.").Replace("://https", ""); + + var matches = Regex.Matches(text, RegexUrlPattern, + RegexOptions.Compiled | RegexOptions.IgnoreCase); + + foreach (Match match in matches) + { + if (!match.Success || match.Groups.Count <= 0) + { + continue; + } + var normalizedFullUrl = NormalizeUrl(match.Groups[0].Value); + + if (!allowedExternalUrls.Any(allowedExternalUrl => + normalizedFullUrl.Contains(NormalizeUrl(allowedExternalUrl), StringComparison.OrdinalIgnoreCase))) + { + throw new UserFriendlyException(L["UnAllowedExternalUrlMessage"]); + } + } + } + + private static string NormalizeUrl(string url) + { + return url.Replace("www.", "").RemovePostFix("/"); + } + private List ConvertCommentsToNestedStructure(List comments) { //TODO: I think this method can be optimized if you use dictionaries instead of straight search @@ -156,46 +191,4 @@ public class CommentPublicAppService : CmsKitPublicAppServiceBase, ICommentPubli { return ObjectMapper.Map(comments.Single(c => c.Comment.Id == commentId).Author); } - - private void CheckExternalUrls(string entityType, string text) - { - if (!CmsCommentOptions.AllowedExternalUrls.TryGetValue(entityType, out var allowedExternalUrls)) - { - return; - } - - var matches = Regex.Matches(text, RegexMarkdownUrlPattern, - RegexOptions.Compiled | RegexOptions.IgnoreCase); - - foreach (Match match in matches) - { - if (!match.Success || match.Groups.Count < 2) - { - continue; - } - - var url = NormalizeUrl(match.Groups[1].Value); - if (!IsExternalUrl(url)) - { - continue; - } - - if (!allowedExternalUrls.Any(allowedExternalUrl => - url.Contains(NormalizeUrl(allowedExternalUrl), StringComparison.OrdinalIgnoreCase))) - { - throw new UserFriendlyException(L["UnAllowedExternalUrlMessage"]); - } - } - } - - private static bool IsExternalUrl(string url) - { - return url.StartsWith("https", StringComparison.InvariantCultureIgnoreCase) || - url.StartsWith("http", StringComparison.InvariantCultureIgnoreCase); - } - - private static string NormalizeUrl(string url) - { - return url.Replace("www.", "").RemovePostFix("/"); - } } diff --git a/modules/cms-kit/test/Volo.CmsKit.Application.Tests/Comments/CommentPublicAppService_Tests.cs b/modules/cms-kit/test/Volo.CmsKit.Application.Tests/Comments/CommentPublicAppService_Tests.cs index ddd913a93f..cbb165c1cd 100644 --- a/modules/cms-kit/test/Volo.CmsKit.Application.Tests/Comments/CommentPublicAppService_Tests.cs +++ b/modules/cms-kit/test/Volo.CmsKit.Application.Tests/Comments/CommentPublicAppService_Tests.cs @@ -62,9 +62,28 @@ public class CommentPublicAppService_Tests : CmsKitApplicationTestBase .ShouldBeTrue(); }); } + + [Theory] + [InlineData("https://abp.io/features")] + public async Task CreateAsync_ShouldCreateComment_If_Url_Allowed(string text) + { + _currentUser.Id.Returns(_cmsKitTestData.User2Id); - [Fact] - public async Task CreateAsync_ShouldThrowUserFriendlyException_If_Url_UnAllowed() + await _commentAppService.CreateAsync( + _cmsKitTestData.EntityType1, + _cmsKitTestData.EntityId1, + new CreateCommentInput + { + RepliedCommentId = null, + Text = text + } + ); + } + + [Theory] + [InlineData("[ABP Community](https://community.abp.io/)")] + [InlineData("docs.abp.io")] + public async Task CreateAsync_ShouldThrowUserFriendlyException_If_Url_UnAllowed(string text) { _currentUser.Id.Returns(_cmsKitTestData.User2Id); @@ -75,7 +94,7 @@ public class CommentPublicAppService_Tests : CmsKitApplicationTestBase new CreateCommentInput { RepliedCommentId = null, - Text = "[ABP Community](https://community.abp.io/)", //not allowed URL + Text = text, //not allowed URL } )); }