1.4.5.2
This commit is contained in:
531
EdgeNode/internal/nodes/http_request_encryption.go
Normal file
531
EdgeNode/internal/nodes/http_request_encryption.go
Normal file
@@ -0,0 +1,531 @@
|
||||
// Copyright 2024 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
|
||||
package nodes
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/TeaOSLab/EdgeNode/internal/encryption"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
|
||||
"github.com/andybalholm/brotli"
|
||||
)
|
||||
|
||||
const encryptionCacheVersion = "xor-v1"
|
||||
|
||||
// processPageEncryption 处理页面加密
|
||||
func (this *HTTPRequest) processPageEncryption(resp *http.Response) error {
|
||||
// 首先检查是否是 /waf/loader.js,如果是则直接跳过(不应该被加密)
|
||||
// 这个检查必须在所有其他检查之前,确保 loader.js 永远不会被加密
|
||||
if strings.Contains(this.URL(), "/waf/loader.js") {
|
||||
remotelogs.Debug("HTTP_REQUEST_ENCRYPTION", "skipping /waf/loader.js, should not be encrypted, URL: "+this.URL())
|
||||
return nil
|
||||
}
|
||||
|
||||
if this.web.Encryption == nil {
|
||||
remotelogs.Debug("HTTP_REQUEST_ENCRYPTION", "encryption config is nil for URL: "+this.URL())
|
||||
return nil
|
||||
}
|
||||
|
||||
if !this.web.Encryption.IsOn {
|
||||
remotelogs.Debug("HTTP_REQUEST_ENCRYPTION", "encryption switch is off for URL: "+this.URL())
|
||||
return nil
|
||||
}
|
||||
|
||||
if !this.web.Encryption.IsEnabled() {
|
||||
remotelogs.Debug("HTTP_REQUEST_ENCRYPTION", "encryption is not enabled for URL: "+this.URL())
|
||||
return nil
|
||||
}
|
||||
|
||||
// 检查 URL 白名单
|
||||
if this.web.Encryption.MatchExcludeURL(this.URL()) {
|
||||
remotelogs.Debug("HTTP_REQUEST_ENCRYPTION", "URL is in exclude list: "+this.URL())
|
||||
return nil
|
||||
}
|
||||
|
||||
// 检查 Content-Type 和 URL
|
||||
contentType := resp.Header.Get("Content-Type")
|
||||
contentTypeLower := strings.ToLower(contentType)
|
||||
urlLower := strings.ToLower(this.URL())
|
||||
|
||||
var isHTML = strings.Contains(contentTypeLower, "text/html")
|
||||
// 判断是否为 JavaScript 文件:通过 Content-Type 或 URL 后缀
|
||||
var isJavaScript = strings.Contains(contentTypeLower, "text/javascript") ||
|
||||
strings.Contains(contentTypeLower, "application/javascript") ||
|
||||
strings.Contains(contentTypeLower, "application/x-javascript") ||
|
||||
strings.Contains(contentTypeLower, "text/ecmascript") ||
|
||||
strings.HasSuffix(urlLower, ".js") ||
|
||||
strings.Contains(urlLower, ".js?") ||
|
||||
strings.Contains(urlLower, ".js&")
|
||||
|
||||
if !isHTML && !isJavaScript {
|
||||
remotelogs.Debug("HTTP_REQUEST_ENCRYPTION", "content type not match, URL: "+this.URL()+", Content-Type: "+contentType)
|
||||
return nil
|
||||
}
|
||||
|
||||
// 检查内容大小(仅处理小于 10MB 的内容)
|
||||
if resp.ContentLength > 0 && resp.ContentLength > 10*1024*1024 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// 读取响应体
|
||||
bodyBytes, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_ = resp.Body.Close()
|
||||
|
||||
// 如果源站返回了压缩内容,先解压再处理
|
||||
decodedBody, decoded, err := decodeResponseBody(bodyBytes, resp.Header.Get("Content-Encoding"))
|
||||
if err == nil && decoded {
|
||||
bodyBytes = decodedBody
|
||||
// 已经解压,移除 Content-Encoding
|
||||
resp.Header.Del("Content-Encoding")
|
||||
}
|
||||
|
||||
// 检查实际大小
|
||||
if len(bodyBytes) > 10*1024*1024 {
|
||||
// 内容太大,恢复原始响应体
|
||||
resp.Body = io.NopCloser(bytes.NewReader(bodyBytes))
|
||||
return nil
|
||||
}
|
||||
|
||||
var encryptedBytes []byte
|
||||
|
||||
// 处理 JavaScript 文件
|
||||
if isJavaScript {
|
||||
remotelogs.Debug("HTTP_REQUEST_ENCRYPTION", "processing JavaScript file, URL: "+this.URL())
|
||||
|
||||
// 检查是否需要加密独立的 JavaScript 文件
|
||||
if this.web.Encryption.Javascript == nil {
|
||||
remotelogs.Debug("HTTP_REQUEST_ENCRYPTION", "Javascript config is nil for URL: "+this.URL())
|
||||
resp.Body = io.NopCloser(bytes.NewReader(bodyBytes))
|
||||
return nil
|
||||
}
|
||||
|
||||
if !this.web.Encryption.Javascript.IsOn {
|
||||
remotelogs.Debug("HTTP_REQUEST_ENCRYPTION", "Javascript encryption is not enabled for URL: "+this.URL())
|
||||
resp.Body = io.NopCloser(bytes.NewReader(bodyBytes))
|
||||
return nil
|
||||
}
|
||||
|
||||
// 检查 URL 匹配
|
||||
if !this.web.Encryption.Javascript.MatchURL(this.URL()) {
|
||||
remotelogs.Debug("HTTP_REQUEST_ENCRYPTION", "URL does not match patterns for URL: "+this.URL())
|
||||
resp.Body = io.NopCloser(bytes.NewReader(bodyBytes))
|
||||
return nil
|
||||
}
|
||||
|
||||
// 跳过 Loader 文件(必须排除,否则 loader.js 会被错误加密)
|
||||
if strings.Contains(this.URL(), "/waf/loader.js") ||
|
||||
strings.Contains(this.URL(), "waf-loader.js") ||
|
||||
strings.Contains(this.URL(), "__WAF_") {
|
||||
remotelogs.Debug("HTTP_REQUEST_ENCRYPTION", "skipping loader file, URL: "+this.URL())
|
||||
resp.Body = io.NopCloser(bytes.NewReader(bodyBytes))
|
||||
return nil
|
||||
}
|
||||
|
||||
// 加密 JavaScript 文件
|
||||
remotelogs.Println("HTTP_REQUEST_ENCRYPTION", "encrypting JavaScript file, URL: "+this.URL())
|
||||
encryptedBytes, err = this.encryptJavaScriptFile(bodyBytes, resp)
|
||||
if err != nil {
|
||||
remotelogs.Warn("HTTP_REQUEST_ENCRYPTION", "encrypt JavaScript file failed: "+err.Error())
|
||||
// 加密失败,恢复原始响应体
|
||||
resp.Body = io.NopCloser(bytes.NewReader(bodyBytes))
|
||||
return nil
|
||||
}
|
||||
remotelogs.Println("HTTP_REQUEST_ENCRYPTION", "JavaScript file encrypted successfully, URL: "+this.URL())
|
||||
} else if isHTML {
|
||||
// 加密 HTML 内容
|
||||
encryptedBytes, err = this.encryptHTMLScripts(bodyBytes, resp)
|
||||
if err != nil {
|
||||
remotelogs.Warn("HTTP_REQUEST_ENCRYPTION", "encrypt HTML failed: "+err.Error())
|
||||
// 加密失败,恢复原始响应体
|
||||
resp.Body = io.NopCloser(bytes.NewReader(bodyBytes))
|
||||
return nil
|
||||
}
|
||||
} else {
|
||||
// 未知类型,恢复原始响应体
|
||||
resp.Body = io.NopCloser(bytes.NewReader(bodyBytes))
|
||||
return nil
|
||||
}
|
||||
|
||||
// 替换响应体
|
||||
resp.Body = io.NopCloser(bytes.NewReader(encryptedBytes))
|
||||
resp.ContentLength = int64(len(encryptedBytes))
|
||||
resp.Header.Set("Content-Length", fmt.Sprintf("%d", len(encryptedBytes)))
|
||||
// 避免旧缓存导致解密算法不匹配
|
||||
resp.Header.Set("Cache-Control", "no-store, no-cache, must-revalidate")
|
||||
// 删除 Content-Encoding(如果存在),因为我们修改了内容
|
||||
resp.Header.Del("Content-Encoding")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// encryptHTMLScripts 加密 HTML 中的脚本
|
||||
func (this *HTTPRequest) encryptHTMLScripts(htmlBytes []byte, resp *http.Response) ([]byte, error) {
|
||||
html := string(htmlBytes)
|
||||
|
||||
// 检查是否需要加密 HTML 脚本
|
||||
if this.web.Encryption.HTML == nil || !this.web.Encryption.HTML.IsOn {
|
||||
return htmlBytes, nil
|
||||
}
|
||||
|
||||
// 检查 URL 匹配
|
||||
if !this.web.Encryption.HTML.MatchURL(this.URL()) {
|
||||
return htmlBytes, nil
|
||||
}
|
||||
|
||||
// 生成密钥
|
||||
remoteIP := this.requestRemoteAddr(true)
|
||||
userAgent := this.RawReq.UserAgent()
|
||||
keyID, actualKey := encryption.GenerateEncryptionKey(remoteIP, userAgent, this.web.Encryption.KeyPolicy)
|
||||
|
||||
// 检查缓存
|
||||
var cacheKey string
|
||||
if this.web.Encryption.Cache != nil && this.web.Encryption.Cache.IsOn {
|
||||
// 生成缓存键:keyID + URL + contentHash
|
||||
contentHash := fmt.Sprintf("%x", bytesHash(htmlBytes))
|
||||
cacheKey = fmt.Sprintf("encrypt_%s_%s_%s_%s", encryptionCacheVersion, keyID, this.URL(), contentHash)
|
||||
|
||||
cache := encryption.SharedEncryptionCache(
|
||||
int(this.web.Encryption.Cache.MaxSize),
|
||||
time.Duration(this.web.Encryption.Cache.TTL)*time.Second,
|
||||
)
|
||||
|
||||
if cached, ok := cache.Get(cacheKey); ok {
|
||||
return cached, nil
|
||||
}
|
||||
}
|
||||
|
||||
// 提取并加密内联脚本
|
||||
if this.web.Encryption.HTML.EncryptInlineScripts {
|
||||
html = this.encryptInlineScripts(html, actualKey, keyID)
|
||||
}
|
||||
|
||||
// 提取并加密外部脚本(通过 src 属性)
|
||||
if this.web.Encryption.HTML.EncryptExternalScripts {
|
||||
html = this.encryptExternalScripts(html, actualKey, keyID)
|
||||
}
|
||||
|
||||
// 注入 Loader
|
||||
html = this.injectLoader(html)
|
||||
|
||||
result := []byte(html)
|
||||
|
||||
// 保存到缓存
|
||||
if this.web.Encryption.Cache != nil && this.web.Encryption.Cache.IsOn && len(cacheKey) > 0 {
|
||||
cache := encryption.SharedEncryptionCache(
|
||||
int(this.web.Encryption.Cache.MaxSize),
|
||||
time.Duration(this.web.Encryption.Cache.TTL)*time.Second,
|
||||
)
|
||||
cache.Set(cacheKey, result, this.web.Encryption.Cache.TTL)
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// encryptInlineScripts 加密内联脚本
|
||||
func (this *HTTPRequest) encryptInlineScripts(html string, key string, keyID string) string {
|
||||
// 匹配 <script>...</script>(不包含 src 属性)
|
||||
scriptRegex := regexp.MustCompile(`(?i)<script(?:\s+[^>]*)?>([\s\S]*?)</script>`)
|
||||
|
||||
return scriptRegex.ReplaceAllStringFunc(html, func(match string) string {
|
||||
// 检查是否有 src 属性(外部脚本)
|
||||
if strings.Contains(strings.ToLower(match), "src=") {
|
||||
return match // 跳过外部脚本
|
||||
}
|
||||
|
||||
// 提取脚本内容
|
||||
contentMatch := regexp.MustCompile(`(?i)<script(?:\s+[^>]*)?>([\s\S]*?)</script>`)
|
||||
matches := contentMatch.FindStringSubmatch(match)
|
||||
if len(matches) < 2 {
|
||||
return match
|
||||
}
|
||||
|
||||
scriptContent := matches[1]
|
||||
|
||||
// 跳过空脚本或仅包含空白字符的脚本
|
||||
if strings.TrimSpace(scriptContent) == "" {
|
||||
return match
|
||||
}
|
||||
|
||||
// 跳过已加密的脚本(包含 __WAF_P__)
|
||||
if strings.Contains(scriptContent, "__WAF_P__") {
|
||||
return match
|
||||
}
|
||||
|
||||
// 加密脚本内容
|
||||
encrypted, err := this.encryptScript(scriptContent, key)
|
||||
if err != nil {
|
||||
return match // 加密失败,返回原始内容
|
||||
}
|
||||
|
||||
// 生成元数据(k 是 keyID,用于缓存;key 是实际密钥,用于解密)
|
||||
meta := fmt.Sprintf(`{"k":"%s","key":"%s","t":%d,"alg":"xor"}`, keyID, key, time.Now().Unix())
|
||||
|
||||
// 替换为加密后的脚本(同步解密执行,保证脚本顺序)
|
||||
return fmt.Sprintf(
|
||||
`<script>(function(){
|
||||
function xorDecodeToString(b64,key){
|
||||
var bin=atob(b64);
|
||||
var out=new Uint8Array(bin.length);
|
||||
for(var i=0;i<bin.length;i++){out[i]=bin.charCodeAt(i)^key.charCodeAt(i%%key.length);}
|
||||
if (typeof TextDecoder !== 'undefined') {
|
||||
return new TextDecoder().decode(out);
|
||||
}
|
||||
var s='';for(var j=0;j<out.length;j++){s+=String.fromCharCode(out[j]);}
|
||||
return s;
|
||||
}
|
||||
try{var meta=%s;var code=xorDecodeToString("%s",meta.key);window.eval(code);}catch(e){console.error('WAF inline decrypt/execute failed',e);}
|
||||
})();</script>`,
|
||||
meta,
|
||||
encrypted,
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
// encryptExternalScripts 加密外部脚本(通过替换 src 为加密后的内容)
|
||||
// 注意:这里我们实际上是将外部脚本的内容内联并加密
|
||||
func (this *HTTPRequest) encryptExternalScripts(html string, key string, keyID string) string {
|
||||
// 匹配 <script src="..."></script>
|
||||
scriptRegex := regexp.MustCompile(`(?i)<script\s+([^>]*src\s*=\s*["']([^"']+)["'][^>]*)>\s*</script>`)
|
||||
|
||||
return scriptRegex.ReplaceAllStringFunc(html, func(match string) string {
|
||||
// 提取 src URL
|
||||
srcMatch := regexp.MustCompile(`(?i)src\s*=\s*["']([^"']+)["']`)
|
||||
srcMatches := srcMatch.FindStringSubmatch(match)
|
||||
if len(srcMatches) < 2 {
|
||||
return match
|
||||
}
|
||||
|
||||
srcURL := srcMatches[1]
|
||||
|
||||
// 跳过已加密的脚本或 Loader
|
||||
if strings.Contains(srcURL, "waf-loader.js") || strings.Contains(srcURL, "__WAF_") {
|
||||
return match
|
||||
}
|
||||
|
||||
// 注意:实际实现中,我们需要获取外部脚本的内容
|
||||
// 这里为了简化,我们只是标记需要加密,实际内容获取需要在响应处理时进行
|
||||
// 当前实现:将外部脚本转换为内联加密脚本的占位符
|
||||
// 实际生产环境需要:1. 获取外部脚本内容 2. 加密 3. 替换
|
||||
|
||||
return match // 暂时返回原始内容,后续可以扩展
|
||||
})
|
||||
}
|
||||
|
||||
// encryptScript 加密脚本内容
|
||||
func (this *HTTPRequest) encryptScript(scriptContent string, key string) (string, error) {
|
||||
// 1. XOR 加密(不压缩,避免浏览器解压依赖导致顺序问题)
|
||||
encrypted := xorEncrypt([]byte(scriptContent), []byte(key))
|
||||
|
||||
// 2. Base64 编码
|
||||
encoded := base64.StdEncoding.EncodeToString(encrypted)
|
||||
|
||||
return encoded, nil
|
||||
}
|
||||
|
||||
// injectLoader 注入 Loader 脚本
|
||||
func (this *HTTPRequest) injectLoader(html string) string {
|
||||
// 检查是否已经注入
|
||||
if strings.Contains(html, "waf-loader.js") {
|
||||
return html
|
||||
}
|
||||
|
||||
// 在 </head> 之前注入,如果没有 </head>,则在 </body> 之前注入
|
||||
// 不使用 async,确保 loader 在解析阶段优先加载并执行
|
||||
loaderScript := `<script src="/waf/loader.js"></script>`
|
||||
|
||||
if strings.Contains(html, "</head>") {
|
||||
return strings.Replace(html, "</head>", loaderScript+"</head>", 1)
|
||||
} else if strings.Contains(html, "</body>") {
|
||||
return strings.Replace(html, "</body>", loaderScript+"</body>", 1)
|
||||
} else {
|
||||
// 如果都没有,在开头注入
|
||||
return loaderScript + html
|
||||
}
|
||||
}
|
||||
|
||||
// compressGzip 使用 Gzip 压缩(浏览器原生支持)
|
||||
func compressGzip(data []byte) ([]byte, error) {
|
||||
var buf bytes.Buffer
|
||||
writer := gzip.NewWriter(&buf)
|
||||
_, err := writer.Write(data)
|
||||
if err != nil {
|
||||
writer.Close()
|
||||
return nil, err
|
||||
}
|
||||
err = writer.Close()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
// decodeResponseBody 根据 Content-Encoding 解压响应体
|
||||
func decodeResponseBody(body []byte, encoding string) ([]byte, bool, error) {
|
||||
enc := strings.ToLower(strings.TrimSpace(encoding))
|
||||
if enc == "" || enc == "identity" {
|
||||
return body, false, nil
|
||||
}
|
||||
switch enc {
|
||||
case "gzip":
|
||||
reader, err := gzip.NewReader(bytes.NewReader(body))
|
||||
if err != nil {
|
||||
return body, false, err
|
||||
}
|
||||
defer reader.Close()
|
||||
decoded, err := io.ReadAll(reader)
|
||||
if err != nil {
|
||||
return body, false, err
|
||||
}
|
||||
return decoded, true, nil
|
||||
case "br":
|
||||
reader := brotli.NewReader(bytes.NewReader(body))
|
||||
decoded, err := io.ReadAll(reader)
|
||||
if err != nil {
|
||||
return body, false, err
|
||||
}
|
||||
return decoded, true, nil
|
||||
default:
|
||||
// 未知编码,保持原样
|
||||
return body, false, nil
|
||||
}
|
||||
}
|
||||
|
||||
// compressBrotli 使用 Brotli 压缩(保留用于其他用途)
|
||||
func compressBrotli(data []byte, level int) ([]byte, error) {
|
||||
var buf bytes.Buffer
|
||||
writer := brotli.NewWriterOptions(&buf, brotli.WriterOptions{
|
||||
Quality: level,
|
||||
LGWin: 14,
|
||||
})
|
||||
_, err := writer.Write(data)
|
||||
if err != nil {
|
||||
writer.Close()
|
||||
return nil, err
|
||||
}
|
||||
err = writer.Close()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
// xorEncrypt XOR 加密
|
||||
func xorEncrypt(data []byte, key []byte) []byte {
|
||||
result := make([]byte, len(data))
|
||||
keyLen := len(key)
|
||||
if keyLen == 0 {
|
||||
return data
|
||||
}
|
||||
|
||||
for i := 0; i < len(data); i++ {
|
||||
result[i] = data[i] ^ key[i%keyLen]
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// encryptJavaScriptFile 加密独立的 JavaScript 文件
|
||||
func (this *HTTPRequest) encryptJavaScriptFile(jsBytes []byte, resp *http.Response) ([]byte, error) {
|
||||
jsContent := string(jsBytes)
|
||||
|
||||
// 跳过空文件
|
||||
if strings.TrimSpace(jsContent) == "" {
|
||||
return jsBytes, nil
|
||||
}
|
||||
|
||||
// 跳过已加密的脚本(包含 __WAF_P__)
|
||||
if strings.Contains(jsContent, "__WAF_P__") {
|
||||
return jsBytes, nil
|
||||
}
|
||||
|
||||
// 生成密钥
|
||||
remoteIP := this.requestRemoteAddr(true)
|
||||
userAgent := this.RawReq.UserAgent()
|
||||
keyID, actualKey := encryption.GenerateEncryptionKey(remoteIP, userAgent, this.web.Encryption.KeyPolicy)
|
||||
|
||||
// 检查缓存
|
||||
var cacheKey string
|
||||
if this.web.Encryption.Cache != nil && this.web.Encryption.Cache.IsOn {
|
||||
// 生成缓存键:keyID + URL + contentHash
|
||||
contentHash := fmt.Sprintf("%x", bytesHash(jsBytes))
|
||||
cacheKey = fmt.Sprintf("encrypt_js_%s_%s_%s_%s", encryptionCacheVersion, keyID, this.URL(), contentHash)
|
||||
|
||||
cache := encryption.SharedEncryptionCache(
|
||||
int(this.web.Encryption.Cache.MaxSize),
|
||||
time.Duration(this.web.Encryption.Cache.TTL)*time.Second,
|
||||
)
|
||||
|
||||
if cached, ok := cache.Get(cacheKey); ok {
|
||||
return cached, nil
|
||||
}
|
||||
}
|
||||
|
||||
// 加密脚本内容
|
||||
encrypted, err := this.encryptScript(jsContent, actualKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 生成元数据(k 是 keyID,用于缓存;key 是实际密钥,用于解密)
|
||||
meta := fmt.Sprintf(`{"k":"%s","key":"%s","t":%d,"alg":"xor"}`, keyID, actualKey, time.Now().Unix())
|
||||
|
||||
// 生成加密后的 JavaScript 代码(同步解密执行,保证脚本顺序)
|
||||
encryptedJS := fmt.Sprintf(`(function() {
|
||||
try {
|
||||
function xorDecodeToString(b64, key) {
|
||||
var bin = atob(b64);
|
||||
var out = new Uint8Array(bin.length);
|
||||
for (var i = 0; i < bin.length; i++) {
|
||||
out[i] = bin.charCodeAt(i) ^ key.charCodeAt(i %% key.length);
|
||||
}
|
||||
if (typeof TextDecoder !== 'undefined') {
|
||||
return new TextDecoder().decode(out);
|
||||
}
|
||||
var s = '';
|
||||
for (var j = 0; j < out.length; j++) {
|
||||
s += String.fromCharCode(out[j]);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
var meta = %s;
|
||||
var code = xorDecodeToString("%s", meta.key);
|
||||
// 使用全局 eval,尽量保持和 <script> 一致的作用域
|
||||
window.eval(code);
|
||||
} catch (e) {
|
||||
console.error('WAF JS decrypt/execute failed', e);
|
||||
}
|
||||
})();`, meta, encrypted)
|
||||
|
||||
result := []byte(encryptedJS)
|
||||
|
||||
// 保存到缓存
|
||||
if this.web.Encryption.Cache != nil && this.web.Encryption.Cache.IsOn && len(cacheKey) > 0 {
|
||||
cache := encryption.SharedEncryptionCache(
|
||||
int(this.web.Encryption.Cache.MaxSize),
|
||||
time.Duration(this.web.Encryption.Cache.TTL)*time.Second,
|
||||
)
|
||||
cache.Set(cacheKey, result, this.web.Encryption.Cache.TTL)
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// bytesHash 计算字节数组的简单哈希(用于缓存键)
|
||||
func bytesHash(data []byte) uint64 {
|
||||
var hash uint64 = 5381
|
||||
for _, b := range data {
|
||||
hash = ((hash << 5) + hash) + uint64(b)
|
||||
}
|
||||
return hash
|
||||
}
|
||||
Reference in New Issue
Block a user