补齐任务列表状态筛选和邮件HTML安全字段
This commit is contained in:
@@ -16,7 +16,9 @@ import java.util.List;
|
||||
* @param sourceSentAt 邮件来源发送时间,UTC
|
||||
* @param textBody 完整纯文本正文
|
||||
* @param htmlBody 完整 HTML 正文,前端展示前必须 sanitize
|
||||
* @param htmlBodySanitized 后端第一版清洗后的 HTML,前端生产展示应优先使用
|
||||
* @param htmlSanitizeRequired 是否要求前端 sanitize HTML
|
||||
* @param htmlRenderMode HTML 渲染建议模式,例如 SANITIZED_HTML 或 TEXT_ONLY
|
||||
* @param inlineImages 内联图片外链
|
||||
* @param attachments 附件外链
|
||||
* @param relatedOrders 关联订单摘要
|
||||
@@ -39,8 +41,12 @@ public record SourceMessageConversationMessageResult(
|
||||
String textBody,
|
||||
@JsonProperty("html_body")
|
||||
String htmlBody,
|
||||
@JsonProperty("html_body_sanitized")
|
||||
String htmlBodySanitized,
|
||||
@JsonProperty("html_sanitize_required")
|
||||
Boolean htmlSanitizeRequired,
|
||||
@JsonProperty("html_render_mode")
|
||||
String htmlRenderMode,
|
||||
@JsonProperty("inline_images")
|
||||
List<SourceMessageOriginalMediaResponse> inlineImages,
|
||||
List<SourceMessageOriginalMediaResponse> attachments,
|
||||
|
||||
@@ -18,6 +18,7 @@ import java.time.LocalDateTime;
|
||||
import java.time.ZoneOffset;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.regex.Pattern;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@@ -31,6 +32,20 @@ public class SourceMessageConversationServiceImpl implements SourceMessageConver
|
||||
private static final String ACCESS_SCENE = "source-message-conversation";
|
||||
private static final String MEDIA_TYPE_INLINE_IMAGE = "INLINE_IMAGE";
|
||||
private static final String MEDIA_TYPE_ATTACHMENT = "ATTACHMENT";
|
||||
private static final String HTML_RENDER_MODE_SANITIZED = "SANITIZED_HTML";
|
||||
private static final String HTML_RENDER_MODE_TEXT_ONLY = "TEXT_ONLY";
|
||||
private static final Pattern DANGEROUS_TAG_PATTERN = Pattern.compile(
|
||||
"(?is)<\\s*(script|style|iframe|object|embed|meta|link|base|form)[^>]*>.*?<\\s*/\\s*\\1\\s*>"
|
||||
+ "|<\\s*(script|style|iframe|object|embed|meta|link|base|form)[^>]*/?\\s*>");
|
||||
private static final Pattern EVENT_ATTRIBUTE_PATTERN = Pattern.compile(
|
||||
"(?i)\\s+on[a-z0-9_-]+\\s*=\\s*(\"[^\"]*\"|'[^']*'|[^\\s>]+)");
|
||||
private static final Pattern STYLE_ATTRIBUTE_PATTERN = Pattern.compile(
|
||||
"(?i)\\s+style\\s*=\\s*(\"[^\"]*\"|'[^']*'|[^\\s>]+)");
|
||||
private static final Pattern DANGEROUS_URL_ATTRIBUTE_PATTERN = Pattern.compile(
|
||||
"(?i)\\s+(href|src|xlink:href|formaction)\\s*=\\s*"
|
||||
+ "(\"\\s*(?:javascript|data|vbscript):[^\"]*\""
|
||||
+ "|'\\s*(?:javascript|data|vbscript):[^']*'"
|
||||
+ "|\\s*(?:javascript|data|vbscript):[^\\s>]+)");
|
||||
|
||||
private final SourceMessageInboxRepository inboxRepository;
|
||||
private final List<SourceMessageRelatedContextProvider> relatedContextProviders;
|
||||
@@ -101,6 +116,7 @@ public class SourceMessageConversationServiceImpl implements SourceMessageConver
|
||||
private SourceMessageConversationMessageResult toConversationMessage(SourceMessageInboxSnapshot message) {
|
||||
SourceMessageOriginalContent originalContent = readOriginalAndAudit(message);
|
||||
SourceMessageRelatedContextResult relatedContext = findRelatedContext(message);
|
||||
String htmlBody = originalContent.htmlBody();
|
||||
return new SourceMessageConversationMessageResult(
|
||||
message.id().toString(),
|
||||
message.externalMessageId(),
|
||||
@@ -110,8 +126,10 @@ public class SourceMessageConversationServiceImpl implements SourceMessageConver
|
||||
UtcTimeFormatter.toUtcOffsetDateTime(message.receivedAt()),
|
||||
UtcTimeFormatter.toUtcOffsetDateTime(message.sourceSentAt()),
|
||||
originalContent.textBody(),
|
||||
originalContent.htmlBody(),
|
||||
htmlBody,
|
||||
sanitizeHtml(htmlBody),
|
||||
true,
|
||||
htmlRenderMode(htmlBody),
|
||||
filterMedia(originalContent.mediaItems(), MEDIA_TYPE_INLINE_IMAGE),
|
||||
filterMedia(originalContent.mediaItems(), MEDIA_TYPE_ATTACHMENT),
|
||||
relatedContext.relatedOrders(),
|
||||
@@ -173,4 +191,27 @@ public class SourceMessageConversationServiceImpl implements SourceMessageConver
|
||||
item.externalUrl(),
|
||||
item.externalMediaId());
|
||||
}
|
||||
|
||||
/**
|
||||
* 第一版 HTML 清洗:保留邮件正文基本结构,移除脚本标签、事件属性和危险协议链接。
|
||||
*/
|
||||
private String sanitizeHtml(String htmlBody) {
|
||||
if (htmlBody == null) {
|
||||
return null;
|
||||
}
|
||||
String sanitized = DANGEROUS_TAG_PATTERN.matcher(htmlBody).replaceAll("");
|
||||
sanitized = EVENT_ATTRIBUTE_PATTERN.matcher(sanitized).replaceAll("");
|
||||
sanitized = STYLE_ATTRIBUTE_PATTERN.matcher(sanitized).replaceAll("");
|
||||
return DANGEROUS_URL_ATTRIBUTE_PATTERN.matcher(sanitized).replaceAll("");
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回前端渲染建议;有 HTML 时优先使用后端清洗后的安全 HTML。
|
||||
*/
|
||||
private String htmlRenderMode(String htmlBody) {
|
||||
if (htmlBody == null || htmlBody.isBlank()) {
|
||||
return HTML_RENDER_MODE_TEXT_ONLY;
|
||||
}
|
||||
return HTML_RENDER_MODE_SANITIZED;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user