99 lines
2.3 KiB
Go
99 lines
2.3 KiB
Go
package testweb
|
||
|
||
import (
|
||
"fmt"
|
||
"os"
|
||
"path/filepath"
|
||
"regexp"
|
||
"sort"
|
||
"strings"
|
||
"time"
|
||
)
|
||
|
||
var logRelRE = regexp.MustCompile(`^\d{4}-\d{2}-\d{2} \d{2}/\d{8,14}_file_[a-f0-9]+\.log$`)
|
||
|
||
// FileLogEntry api-logs 中的 file 通道日志条目。
|
||
type FileLogEntry struct {
|
||
Rel string `json:"rel"`
|
||
Name string `json:"name"`
|
||
HourDir string `json:"hourDir"`
|
||
Modified string `json:"modified"`
|
||
Size int64 `json:"size"`
|
||
}
|
||
|
||
// ListFileLogs 列出 api-logs 下所有 *_file_*.log(按修改时间倒序)。
|
||
func ListFileLogs(root string) ([]FileLogEntry, error) {
|
||
root, err := filepath.Abs(root)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
var entries []FileLogEntry
|
||
err = filepath.WalkDir(root, func(path string, d os.DirEntry, walkErr error) error {
|
||
if walkErr != nil {
|
||
return walkErr
|
||
}
|
||
if d.IsDir() {
|
||
return nil
|
||
}
|
||
name := d.Name()
|
||
if !strings.Contains(name, "_file_") || !strings.HasSuffix(name, ".log") {
|
||
return nil
|
||
}
|
||
rel, err := filepath.Rel(root, path)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
rel = filepath.ToSlash(rel)
|
||
if !logRelRE.MatchString(rel) {
|
||
return nil
|
||
}
|
||
info, err := d.Info()
|
||
if err != nil {
|
||
return err
|
||
}
|
||
hourDir := filepath.ToSlash(filepath.Dir(rel))
|
||
entries = append(entries, FileLogEntry{
|
||
Rel: rel,
|
||
Name: name,
|
||
HourDir: hourDir,
|
||
Modified: info.ModTime().Format(time.RFC3339),
|
||
Size: info.Size(),
|
||
})
|
||
return nil
|
||
})
|
||
if err != nil {
|
||
if os.IsNotExist(err) {
|
||
return nil, nil
|
||
}
|
||
return nil, err
|
||
}
|
||
sort.Slice(entries, func(i, j int) bool {
|
||
return entries[i].Modified > entries[j].Modified
|
||
})
|
||
return entries, nil
|
||
}
|
||
|
||
// resolveLogRelPath 校验 rel 并返回绝对路径。
|
||
func resolveLogRelPath(root, rel string) (string, error) {
|
||
rel = filepath.ToSlash(strings.TrimSpace(rel))
|
||
if rel == "" || strings.Contains(rel, "..") {
|
||
return "", fmt.Errorf("invalid rel path")
|
||
}
|
||
if !logRelRE.MatchString(rel) {
|
||
return "", fmt.Errorf("rel path not allowed")
|
||
}
|
||
root, err := filepath.Abs(root)
|
||
if err != nil {
|
||
return "", err
|
||
}
|
||
full := filepath.Join(root, filepath.FromSlash(rel))
|
||
full, err = filepath.Abs(full)
|
||
if err != nil {
|
||
return "", err
|
||
}
|
||
if !strings.HasPrefix(full, root+string(os.PathSeparator)) && full != root {
|
||
return "", fmt.Errorf("path escape")
|
||
}
|
||
return full, nil
|
||
}
|