75 lines
2.4 KiB
Go
75 lines
2.4 KiB
Go
package domain
|
||
|
||
import (
|
||
"hash/fnv"
|
||
"net/url"
|
||
"strings"
|
||
)
|
||
|
||
// NormalizeWebPageURL 把用户消息里的链接规范化为去重用的稳定形态:仅接受 http/https、
|
||
// 拒绝带 userinfo 的 URL(SSRF/伪装防御)、小写 scheme 与 host、去掉默认端口与 fragment,
|
||
// 保留 path 与 query 原样。返回规范化 URL 与是否可预览(不可预览返回 false)。
|
||
func NormalizeWebPageURL(raw string) (string, bool) {
|
||
raw = strings.TrimSpace(raw)
|
||
if raw == "" {
|
||
return "", false
|
||
}
|
||
u, err := url.Parse(raw)
|
||
if err != nil {
|
||
return "", false
|
||
}
|
||
scheme := strings.ToLower(u.Scheme)
|
||
if scheme != "http" && scheme != "https" {
|
||
return "", false
|
||
}
|
||
if u.User != nil {
|
||
return "", false
|
||
}
|
||
host := strings.ToLower(u.Host)
|
||
if host == "" {
|
||
return "", false
|
||
}
|
||
// 去掉与 scheme 对应的默认端口,避免 example.com 与 example.com:80 哈希不同。
|
||
if h, port, ok := splitHostPort(host); ok {
|
||
if (scheme == "http" && port == "80") || (scheme == "https" && port == "443") {
|
||
host = h
|
||
}
|
||
}
|
||
out := scheme + "://" + host + u.EscapedPath()
|
||
if u.RawQuery != "" {
|
||
out += "?" + u.RawQuery
|
||
}
|
||
return out, true
|
||
}
|
||
|
||
// splitHostPort 拆分 host:port;无端口时 ok=false。不用 net.SplitHostPort 以免 IPv6
|
||
// 字面量裸 host(无端口)被当作错误。
|
||
func splitHostPort(host string) (string, string, bool) {
|
||
// IPv6 字面量形如 [::1]:443;只在末尾 ] 之后找端口。
|
||
idx := strings.LastIndexByte(host, ':')
|
||
if idx < 0 {
|
||
return host, "", false
|
||
}
|
||
if close := strings.LastIndexByte(host, ']'); close >= 0 && idx < close {
|
||
return host, "", false
|
||
}
|
||
return host[:idx], host[idx+1:], true
|
||
}
|
||
|
||
// IsPendingWebPageMedia 报告 media 是否为 ID==id 的 pending 链接预览占位(异步解析的
|
||
// 幂等守卫:仅这种状态才允许被解析结果就地替换)。
|
||
func IsPendingWebPageMedia(m *MessageMedia, id int64) bool {
|
||
return m != nil &&
|
||
m.Kind == MessageMediaKindWebPage &&
|
||
m.WebPage != nil &&
|
||
m.WebPage.State == MessageWebPageStatePending &&
|
||
m.WebPage.ID == id
|
||
}
|
||
|
||
// WebPageURLHash 计算规范化 URL 的稳定 63-bit 哈希,同时用作 web_pages 行的主键与
|
||
// webPage id(保证 pending 占位与 done 解析携带同一 id)。FNV-1a/64 取正。
|
||
func WebPageURLHash(normalized string) int64 {
|
||
h := fnv.New64a()
|
||
_, _ = h.Write([]byte(normalized))
|
||
return int64(h.Sum64() & 0x7fffffffffffffff)
|
||
}
|