refactor(template): Enhance template loading security and error messages

This commit is contained in:
2025-10-15 16:40:07 +11:00
parent fce75baed4
commit 23a85f573b
4 changed files with 27 additions and 11 deletions

View File

@@ -133,8 +133,8 @@ func NewTemplateLoadError(template string, cause error) *CommonError {
return NewError(ErrTemplateLoad, fmt.Sprintf("failed to load template: %s", template), cause) return NewError(ErrTemplateLoad, fmt.Sprintf("failed to load template: %s", template), cause)
} }
func NewTemplateParseError(cause error) *CommonError { func NewTemplateParseError(data []byte, cause error) *CommonError {
return NewError(ErrTemplateParse, "failed to parse template", cause) return NewError(ErrTemplateParse, fmt.Sprintf("failed to parse template: %s", data), cause)
} }
// Subscription errors // Subscription errors
@@ -142,8 +142,8 @@ func NewSubscriptionLoadError(url string, cause error) *CommonError {
return NewError(ErrSubscriptionLoad, fmt.Sprintf("failed to load subscription: %s", url), cause) return NewError(ErrSubscriptionLoad, fmt.Sprintf("failed to load subscription: %s", url), cause)
} }
func NewSubscriptionParseError(cause error) *CommonError { func NewSubscriptionParseError(data []byte, cause error) *CommonError {
return NewError(ErrSubscriptionParse, "failed to parse subscription", cause) return NewError(ErrSubscriptionParse, fmt.Sprintf("failed to parse subscription: %s", string(data)), cause)
} }
// Regex errors // Regex errors

View File

@@ -129,7 +129,7 @@ func BuildSub(clashType model.ClashType, query model.ConvertConfig, template str
err = yaml.Unmarshal(templateBytes, &temp) err = yaml.Unmarshal(templateBytes, &temp)
if err != nil { if err != nil {
logger.Logger.Debug("parse template failed", zap.Error(err)) logger.Logger.Debug("parse template failed", zap.Error(err))
return nil, NewTemplateParseError(err) return nil, NewTemplateParseError(templateBytes, err)
} }
var proxyList []P.Proxy var proxyList []P.Proxy
@@ -168,7 +168,7 @@ func BuildSub(clashType model.ClashType, query model.ConvertConfig, template str
zap.String("data", string(data)), zap.String("data", string(data)),
zap.Error(err), zap.Error(err),
) )
return nil, NewSubscriptionParseError(err) return nil, NewSubscriptionParseError(data, err)
} }
p, err := parser.ParseProxies(parser.ParseConfig{UseUDP: query.UseUDP}, strings.Split(base64, "\n")...) p, err := parser.ParseProxies(parser.ParseConfig{UseUDP: query.UseUDP}, strings.Split(base64, "\n")...)
if err != nil { if err != nil {

View File

@@ -3,11 +3,27 @@ package common
import ( import (
"io" "io"
"os" "os"
"path/filepath"
"strings"
) )
func LoadTemplate(templatePath string) ([]byte, error) { const templatesDir = "templates"
if _, err := os.Stat(templatePath); err == nil {
file, err := os.Open(templatePath) // LoadTemplate 只读取运行目录下的 templates 目录,防止其他文件内容泄漏
func LoadTemplate(templateName string) ([]byte, error) {
// 清理路径,防止目录遍历攻击
cleanTemplateName := filepath.Clean(templateName)
// 检查是否尝试访问父目录
if strings.HasPrefix(cleanTemplateName, "..") || strings.Contains(cleanTemplateName, string(filepath.Separator)+".."+string(filepath.Separator)) {
return nil, NewFileNotFoundError(templateName) // 拒绝包含父目录的路径
}
// 构建完整路径,确保只从 templates 目录读取
fullPath := filepath.Join(templatesDir, cleanTemplateName)
if _, err := os.Stat(fullPath); err == nil {
file, err := os.Open(fullPath)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -22,5 +38,5 @@ func LoadTemplate(templatePath string) ([]byte, error) {
} }
return result, nil return result, nil
} }
return nil, NewFileNotFoundError(templatePath) return nil, NewFileNotFoundError(templateName)
} }

View File

@@ -89,7 +89,7 @@
<!-- Template --> <!-- Template -->
<div class="form-group mb-3"> <div class="form-group mb-3">
<label for="template">模板链接或名称:</label> <label for="template">模板链接或名称:</label>
<input class="form-control" id="template" name="template" placeholder="输入外部模板链接或内部模板名称(可选)" type="text" /> <input class="form-control" id="template" name="template" placeholder="输入模板链接(可选)" type="text" />
</div> </div>
<!-- Subscription Link --> <!-- Subscription Link -->
<div class="form-group mb-3"> <div class="form-group mb-3">