Smail/backend/hooks/wechat_push/verification_code.go
smail 781e2c1559
Some checks are pending
Security Checks / security (push) Waiting to run
微信验证码提取基础实现检查点
2026-09-27 23:53:33 +08:00

87 lines
2.8 KiB
Go

package main
import (
"bytes"
"regexp"
"strings"
"git.pcux.cn/xuans/Smail/dto/parsemail"
"golang.org/x/net/html"
)
const maxWeChatVerificationScanBytes = 64 << 10
const weChatVerificationKeyword = `(?:验证码|校验码|动态码|认证码|一次性(?:密码|口令)|verification[\s-]*code|security[\s-]*code|authentication[\s-]*code|one[\s-]*time[\s-]*(?:password|code)|passcode|\botp\b)`
var weChatVerificationPatterns = []*regexp.Regexp{
regexp.MustCompile(`(?i)` + weChatVerificationKeyword + `(?:[[:space:][:punct:]:,。!【】()]|是|为|is|equals|如下|your|please|use){0,20}\b([A-Z0-9]{4,8})\b`),
regexp.MustCompile(`(?i)\b([A-Z0-9]{4,8})\b(?:[[:space:][:punct:]:,。!【】()]|是|为|is|your|您的){0,20}` + weChatVerificationKeyword),
}
func extractWeChatVerificationCode(email *parsemail.Email) (string, bool) {
if email == nil {
return "", false
}
// 分别检查标题和各正文,避免跨字段拼接出不存在的验证码。
if code, ok := findWeChatVerificationCode(truncateWeChatText(email.Subject, maxWeChatVerificationScanBytes)); ok {
return code, true
}
text := email.Text[:min(len(email.Text), maxWeChatVerificationScanBytes)]
if code, ok := findWeChatVerificationCode(string(text)); ok {
return code, true
}
return findWeChatVerificationCode(weChatHTMLNotificationText(email.HTML))
}
func findWeChatVerificationCode(content string) (string, bool) {
for _, pattern := range weChatVerificationPatterns {
for _, match := range pattern.FindAllStringSubmatch(content, -1) {
code := match[1]
// 排除普通英文单词,只识别至少含一个数字的四至八位字母数字组合。
if strings.ContainsAny(code, "0123456789") {
return code, true
}
}
}
return "", false
}
func weChatHTMLNotificationText(content []byte) string {
// 限制原始输入,避免大邮件在通知提取阶段产生无界扫描和额外分配。
content = content[:min(len(content), maxWeChatVerificationScanBytes)]
tokenizer := html.NewTokenizer(bytes.NewReader(content))
var text strings.Builder
hiddenDepth := 0
for {
switch tokenizer.Next() {
case html.ErrorToken:
return text.String()
case html.StartTagToken:
name, _ := tokenizer.TagName()
if weChatHiddenHTMLTag(string(name)) {
hiddenDepth++
}
case html.EndTagToken:
name, _ := tokenizer.TagName()
if hiddenDepth > 0 && weChatHiddenHTMLTag(string(name)) {
hiddenDepth--
}
case html.TextToken:
if hiddenDepth == 0 {
// 分词器负责解码实体;不读取属性、注释、脚本及样式中的内容。
text.Write(tokenizer.Text())
text.WriteByte(' ')
}
}
}
}
func weChatHiddenHTMLTag(name string) bool {
switch name {
case "head", "script", "style", "template":
return true
default:
return false
}
}