Files
MeBox/internal/service/reader/rule/files_test.go
T
truewhile 2b2dc697ce bug处理
2026-10-08 11:13:10 +08:00

153 lines
4.6 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package rule
import (
"io"
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"strings"
"sync/atomic"
"testing"
"golang.org/x/text/encoding/simplifiedchinese"
)
// 本文件:java 文件接口(cacheFile / downloadFile / readTxtFile / deleteFile)
// 的语义回归。对应 legado JsExtensions:
// - cacheFile(url) → 返回文件**文本内容**(不是路径);
// - downloadFile(url) → 返回相对缓存根目录的路径;
// - readTxtFile(path[, charset]) → 返回文本;
// - 只允许访问缓存目录内的文件。
func newFileTestRunner(cacheDir, baseURL string, hits *int32) *JSRunner {
return NewJSRunner(JSConfig{
CacheDir: cacheDir,
BaseURL: baseURL,
Fetch: func(req *Request) (string, string, int, error) {
if hits != nil {
atomic.AddInt32(hits, 1)
}
resp, err := http.Get(req.URL) //nolint:gosec // 测试内固定 httptest 地址
if err != nil {
return "", "", 0, err
}
defer resp.Body.Close()
b, _ := io.ReadAll(resp.Body)
return string(b), req.URL, resp.StatusCode, nil
},
})
}
func TestCacheFileReturnsTextContent(t *testing.T) {
var hits int32
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
_, _ = w.Write([]byte("第一章 开始\n第二章 继续"))
}))
defer srv.Close()
r := newFileTestRunner(t.TempDir(), srv.URL, &hits)
ar := NewAnalyzeRule()
v, err := r.Run(ar, "java.cacheFile('"+srv.URL+"/vol.txt')", nil, "")
if err != nil {
t.Fatalf("cacheFile 失败: %v", err)
}
if got := anyToString(v); got != "第一章 开始\n第二章 继续" {
t.Fatalf("cacheFile 应返回文本内容,实际 %q", got)
}
// 第二次应命中缓存,不再发起请求。
if _, err := r.Run(ar, "java.cacheFile('"+srv.URL+"/vol.txt')", nil, ""); err != nil {
t.Fatal(err)
}
if n := atomic.LoadInt32(&hits); n != 1 {
t.Fatalf("重复调用应命中缓存(请求次数=%d)", n)
}
}
func TestCacheFileDecodesGBK(t *testing.T) {
gbk, err := simplifiedchinese.GBK.NewEncoder().Bytes([]byte("中文内容测试"))
if err != nil {
t.Fatal(err)
}
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
_, _ = w.Write(gbk)
}))
defer srv.Close()
r := newFileTestRunner(t.TempDir(), srv.URL, nil)
v, err := r.Run(NewAnalyzeRule(), "java.cacheFile('"+srv.URL+"/gbk.txt')", nil, "")
if err != nil {
t.Fatal(err)
}
if got := anyToString(v); got != "中文内容测试" {
t.Fatalf("GBK 文本未正确解码: %q", got)
}
}
func TestDownloadFileThenReadTxtFile(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
_, _ = w.Write([]byte("正文内容"))
}))
defer srv.Close()
cacheDir := t.TempDir()
r := newFileTestRunner(cacheDir, srv.URL, nil)
ar := NewAnalyzeRule()
rawPath, err := r.Run(ar, "java.downloadFile('"+srv.URL+"/a.txt')", nil, "")
if err != nil {
t.Fatal(err)
}
rel := anyToString(rawPath)
if !strings.HasPrefix(rel, "/") || !strings.HasSuffix(rel, ".txt") {
t.Fatalf("downloadFile 应返回相对缓存路径,实际 %q", rel)
}
if _, err := os.Stat(filepath.Join(cacheDir, "reader", "files", strings.TrimPrefix(rel, "/"))); err != nil {
t.Fatalf("文件未落到缓存目录: %v", err)
}
v, err := r.Run(ar, "java.readTxtFile('"+rel+"')", nil, "")
if err != nil {
t.Fatal(err)
}
if got := anyToString(v); got != "正文内容" {
t.Fatalf("readTxtFile = %q", got)
}
del, err := r.Run(ar, "String(java.deleteFile('"+rel+"'))", nil, "")
if err != nil {
t.Fatal(err)
}
if anyToString(del) != "true" {
t.Fatalf("deleteFile 应返回 true,实际 %q", anyToString(del))
}
}
func TestFileOpsRejectPathTraversal(t *testing.T) {
cacheDir := t.TempDir()
secret := filepath.Join(cacheDir, "secret.txt")
if err := os.WriteFile(secret, []byte("不该被读到"), 0o600); err != nil {
t.Fatal(err)
}
r := newFileTestRunner(cacheDir, "", nil)
ar := NewAnalyzeRule()
for _, p := range []string{"/../../secret.txt", "../secret.txt", "/../reader/files/../../secret.txt"} {
v, err := r.Run(ar, "java.readTxtFile('"+p+"')", nil, "")
if err != nil {
t.Fatalf("%s 不应报错: %v", p, err)
}
if got := anyToString(v); strings.Contains(got, "不该被读到") {
t.Fatalf("越权读到了缓存目录外的文件(%s)", p)
}
}
}
func TestFileOpsWithoutCacheDirFail(t *testing.T) {
r := NewJSRunner(JSConfig{})
_, err := r.Run(NewAnalyzeRule(), "java.cacheFile('https://example.com/a.txt')", nil, "")
if err == nil || !strings.Contains(err.Error(), "缓存目录") {
t.Fatalf("未配置缓存目录时应明确报错,实际 %v", err)
}
}