diff --git a/kernel/api/bazaar.go b/kernel/api/bazaar.go index 9d00cb985..26fbc9872 100644 --- a/kernel/api/bazaar.go +++ b/kernel/api/bazaar.go @@ -25,6 +25,14 @@ import ( "github.com/siyuan-note/siyuan/kernel/util" ) +var validPackageTypes = map[string]bool{ + "plugins": true, + "themes": true, + "icons": true, + "templates": true, + "widgets": true, +} + func batchUpdatePackage(c *gin.Context) { ret := gulu.Ret.NewResult() defer c.JSON(http.StatusOK, ret) @@ -34,7 +42,10 @@ func batchUpdatePackage(c *gin.Context) { return } - frontend := arg["frontend"].(string) + var frontend string + if !util.ParseJsonArgs(arg, ret, util.BindJsonArg("frontend", true, &frontend)) { + return + } model.BatchUpdateBazaarPackages(frontend) } @@ -47,7 +58,10 @@ func getUpdatedPackage(c *gin.Context) { return } - frontend := arg["frontend"].(string) + var frontend string + if !util.ParseJsonArgs(arg, ret, util.BindJsonArg("frontend", true, &frontend)) { + return + } plugins, widgets, icons, themes, templates := model.UpdatedPackages(frontend) ret.Data = map[string]interface{}{ "plugins": plugins, @@ -67,11 +81,21 @@ func getBazaarPackageREADME(c *gin.Context) { return } - repoURL := arg["repoURL"].(string) - repoHash := arg["repoHash"].(string) - packageType := arg["packageType"].(string) + var repoURL, repoHash, pkgType string + if !util.ParseJsonArgs(arg, ret, + util.BindJsonArg("repoURL", true, &repoURL), + util.BindJsonArg("repoHash", true, &repoHash), + util.BindJsonArg("packageType", true, &pkgType), + ) { + return + } + if !validPackageTypes[pkgType] { + ret.Code = -1 + ret.Msg = "Invalid package type" + return + } ret.Data = map[string]interface{}{ - "html": model.GetPackageREADME(repoURL, repoHash, packageType), + "html": model.GetPackageREADME(repoURL, repoHash, pkgType), } } @@ -84,10 +108,12 @@ func getBazaarPlugin(c *gin.Context) { return } - frontend := arg["frontend"].(string) - var keyword string - if keywordArg := arg["keyword"]; nil != keywordArg { - keyword = keywordArg.(string) + var frontend, keyword string + if !util.ParseJsonArgs(arg, ret, + util.BindJsonArg("frontend", true, &frontend), + util.BindJsonArg("keyword", false, &keyword), + ) { + return } ret.Data = map[string]interface{}{ @@ -104,10 +130,12 @@ func getInstalledPlugin(c *gin.Context) { return } - frontend := arg["frontend"].(string) - var keyword string - if keywordArg := arg["keyword"]; nil != keywordArg { - keyword = keywordArg.(string) + var frontend, keyword string + if !util.ParseJsonArgs(arg, ret, + util.BindJsonArg("frontend", true, &frontend), + util.BindJsonArg("keyword", false, &keyword), + ) { + return } ret.Data = map[string]interface{}{ @@ -124,14 +152,16 @@ func installBazaarPlugin(c *gin.Context) { return } - var keyword string - if keywordArg := arg["keyword"]; nil != keywordArg { - keyword = keywordArg.(string) + var frontend, keyword, repoURL, repoHash, packageName string + if !util.ParseJsonArgs(arg, ret, + util.BindJsonArg("frontend", true, &frontend), + util.BindJsonArg("keyword", false, &keyword), + util.BindJsonArg("repoURL", true, &repoURL), + util.BindJsonArg("repoHash", true, &repoHash), + util.BindJsonArg("packageName", true, &packageName), + ) { + return } - - repoURL := arg["repoURL"].(string) - repoHash := arg["repoHash"].(string) - packageName := arg["packageName"].(string) err := model.InstallBazaarPlugin(repoURL, repoHash, packageName) if err != nil { ret.Code = 1 @@ -139,8 +169,6 @@ func installBazaarPlugin(c *gin.Context) { return } - frontend := arg["frontend"].(string) - util.PushMsg(model.Conf.Language(69), 3000) ret.Data = map[string]interface{}{ "packages": model.BazaarPlugins(frontend, keyword), @@ -156,13 +184,14 @@ func uninstallBazaarPlugin(c *gin.Context) { return } - var keyword string - if keywordArg := arg["keyword"]; nil != keywordArg { - keyword = keywordArg.(string) + var frontend, keyword, packageName string + if !util.ParseJsonArgs(arg, ret, + util.BindJsonArg("frontend", true, &frontend), + util.BindJsonArg("keyword", false, &keyword), + util.BindJsonArg("packageName", true, &packageName), + ) { + return } - - frontend := arg["frontend"].(string) - packageName := arg["packageName"].(string) err := model.UninstallBazaarPlugin(packageName, frontend) if err != nil { ret.Code = -1 @@ -185,8 +214,8 @@ func getBazaarWidget(c *gin.Context) { } var keyword string - if keywordArg := arg["keyword"]; nil != keywordArg { - keyword = keywordArg.(string) + if !util.ParseJsonArgs(arg, ret, util.BindJsonArg("keyword", false, &keyword)) { + return } ret.Data = map[string]interface{}{ @@ -204,8 +233,8 @@ func getInstalledWidget(c *gin.Context) { } var keyword string - if keywordArg := arg["keyword"]; nil != keywordArg { - keyword = keywordArg.(string) + if !util.ParseJsonArgs(arg, ret, util.BindJsonArg("keyword", false, &keyword)) { + return } ret.Data = map[string]interface{}{ @@ -222,14 +251,15 @@ func installBazaarWidget(c *gin.Context) { return } - var keyword string - if keywordArg := arg["keyword"]; nil != keywordArg { - keyword = keywordArg.(string) + var keyword, repoURL, repoHash, packageName string + if !util.ParseJsonArgs(arg, ret, + util.BindJsonArg("keyword", false, &keyword), + util.BindJsonArg("repoURL", true, &repoURL), + util.BindJsonArg("repoHash", true, &repoHash), + util.BindJsonArg("packageName", true, &packageName), + ) { + return } - - repoURL := arg["repoURL"].(string) - repoHash := arg["repoHash"].(string) - packageName := arg["packageName"].(string) err := model.InstallBazaarWidget(repoURL, repoHash, packageName) if err != nil { ret.Code = 1 @@ -252,12 +282,13 @@ func uninstallBazaarWidget(c *gin.Context) { return } - var keyword string - if keywordArg := arg["keyword"]; nil != keywordArg { - keyword = keywordArg.(string) + var keyword, packageName string + if !util.ParseJsonArgs(arg, ret, + util.BindJsonArg("keyword", false, &keyword), + util.BindJsonArg("packageName", true, &packageName), + ) { + return } - - packageName := arg["packageName"].(string) err := model.UninstallBazaarWidget(packageName) if err != nil { ret.Code = -1 @@ -280,8 +311,8 @@ func getBazaarIcon(c *gin.Context) { } var keyword string - if keywordArg := arg["keyword"]; nil != keywordArg { - keyword = keywordArg.(string) + if !util.ParseJsonArgs(arg, ret, util.BindJsonArg("keyword", false, &keyword)) { + return } ret.Data = map[string]interface{}{ @@ -299,8 +330,8 @@ func getInstalledIcon(c *gin.Context) { } var keyword string - if keywordArg := arg["keyword"]; nil != keywordArg { - keyword = keywordArg.(string) + if !util.ParseJsonArgs(arg, ret, util.BindJsonArg("keyword", false, &keyword)) { + return } ret.Data = map[string]interface{}{ @@ -317,14 +348,15 @@ func installBazaarIcon(c *gin.Context) { return } - var keyword string - if keywordArg := arg["keyword"]; nil != keywordArg { - keyword = keywordArg.(string) + var keyword, repoURL, repoHash, packageName string + if !util.ParseJsonArgs(arg, ret, + util.BindJsonArg("keyword", false, &keyword), + util.BindJsonArg("repoURL", true, &repoURL), + util.BindJsonArg("repoHash", true, &repoHash), + util.BindJsonArg("packageName", true, &packageName), + ) { + return } - - repoURL := arg["repoURL"].(string) - repoHash := arg["repoHash"].(string) - packageName := arg["packageName"].(string) err := model.InstallBazaarIcon(repoURL, repoHash, packageName) if err != nil { ret.Code = 1 @@ -348,12 +380,13 @@ func uninstallBazaarIcon(c *gin.Context) { return } - var keyword string - if keywordArg := arg["keyword"]; nil != keywordArg { - keyword = keywordArg.(string) + var keyword, packageName string + if !util.ParseJsonArgs(arg, ret, + util.BindJsonArg("keyword", false, &keyword), + util.BindJsonArg("packageName", true, &packageName), + ) { + return } - - packageName := arg["packageName"].(string) err := model.UninstallBazaarIcon(packageName) if err != nil { ret.Code = -1 @@ -377,8 +410,8 @@ func getBazaarTemplate(c *gin.Context) { } var keyword string - if keywordArg := arg["keyword"]; nil != keywordArg { - keyword = keywordArg.(string) + if !util.ParseJsonArgs(arg, ret, util.BindJsonArg("keyword", false, &keyword)) { + return } ret.Data = map[string]interface{}{ @@ -396,8 +429,8 @@ func getInstalledTemplate(c *gin.Context) { } var keyword string - if keywordArg := arg["keyword"]; nil != keywordArg { - keyword = keywordArg.(string) + if !util.ParseJsonArgs(arg, ret, util.BindJsonArg("keyword", false, &keyword)) { + return } ret.Data = map[string]interface{}{ @@ -510,15 +543,17 @@ func installBazaarTheme(c *gin.Context) { return } - var keyword string - if keywordArg := arg["keyword"]; nil != keywordArg { - keyword = keywordArg.(string) + var keyword, repoURL, repoHash, packageName string + var mode float64 + if !util.ParseJsonArgs(arg, ret, + util.BindJsonArg("keyword", false, &keyword), + util.BindJsonArg("repoURL", true, &repoURL), + util.BindJsonArg("repoHash", true, &repoHash), + util.BindJsonArg("packageName", true, &packageName), + util.BindJsonArg("mode", true, &mode), + ) { + return } - - repoURL := arg["repoURL"].(string) - repoHash := arg["repoHash"].(string) - packageName := arg["packageName"].(string) - mode := arg["mode"].(float64) update := false if nil != arg["update"] { update = arg["update"].(bool) @@ -530,6 +565,7 @@ func installBazaarTheme(c *gin.Context) { return } + // TODO 安装新主题之后,不应该始终取消外观模式“跟随系统” https://github.com/siyuan-note/siyuan/issues/16990 // 安装集市主题后不跟随系统切换外观模式 model.Conf.Appearance.ModeOS = false model.Conf.Save() diff --git a/kernel/util/net.go b/kernel/util/net.go index 1f39ca627..a57dfa58a 100644 --- a/kernel/util/net.go +++ b/kernel/util/net.go @@ -17,6 +17,7 @@ package util import ( + "fmt" "net" "net/http" "net/url" @@ -213,6 +214,58 @@ func JsonArg(c *gin.Context, result *gulu.Result) (arg map[string]interface{}, o return } +// ParseJsonArg 使用泛型从 JSON 参数中提取指定键的值。 +// - 如果 required 为 true 但参数缺失,则会在 ret.Msg 中写入 “[key] is required” +// - 如果参数存在但类型不匹配,则会在 ret.Msg 中写入 “[key] should be [T]” +// - 返回值 ok 为 false 时,表示提取失败或类型不匹配 +func ParseJsonArg[T any](key string, required bool, arg map[string]interface{}, ret *gulu.Result) (value T, ok bool) { + raw, exists := arg[key] + if !exists || raw == nil { + if required { + ret.Code = -1 + ret.Msg = key + " is required" + } else { + ok = true + } + return + } + + value, ok = raw.(T) + if !ok { + var zero T + ret.Code = -1 + ret.Msg = fmt.Sprintf("%s should be %T", key, zero) + } + return +} + +// JsonArgParseFunc 为单次提取函数,用于 ParseJsonArgs 批量提取。 +type JsonArgParseFunc func(arg map[string]interface{}, ret *gulu.Result) bool + +// BindJsonArg 创建一个提取函数:从 arg 取 key 并写入 dest,供 ParseJsonArgs 使用。 +func BindJsonArg[T any](key string, required bool, dest *T) JsonArgParseFunc { + return func(arg map[string]interface{}, ret *gulu.Result) bool { + v, ok := ParseJsonArg[T](key, required, arg, ret) + if !ok { + return false + } + *dest = v + return true + } +} + +// ParseJsonArgs 按顺序执行多个提取函数。 +// - 任一失败返回 false 并在 ret 中写入错误信息 +// - 全部成功返回 true +func ParseJsonArgs(arg map[string]interface{}, ret *gulu.Result, extractors ...JsonArgParseFunc) bool { + for _, ext := range extractors { + if !ext(arg, ret) { + return false + } + } + return true +} + func InvalidIDPattern(idArg string, result *gulu.Result) bool { if ast.IsNodeIDPattern(idArg) { return false