|
|
|
@ -25,7 +25,8 @@ namespace Volo.CmsKit.Public.Comments;
|
|
|
|
|
[RequiresGlobalFeature(typeof(CommentsFeature))]
|
|
|
|
|
public class CommentPublicAppService : CmsKitPublicAppServiceBase, ICommentPublicAppService
|
|
|
|
|
{
|
|
|
|
|
protected string RegexMarkdownUrlPattern = @"\[[^\]]*\]\((?<url>.*?)\)(?![^\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<CommentWithDetailsDto> ConvertCommentsToNestedStructure(List<CommentWithAuthorQueryResultItem> 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<CmsUser, CmsUserDto>(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("/");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|