diff options
Diffstat (limited to 'modules/templates/helper.go')
-rw-r--r-- | modules/templates/helper.go | 88 |
1 files changed, 62 insertions, 26 deletions
diff --git a/modules/templates/helper.go b/modules/templates/helper.go index 880769dc65..609407d36b 100644 --- a/modules/templates/helper.go +++ b/modules/templates/helper.go @@ -38,7 +38,7 @@ func NewFuncMap() template.FuncMap { "Iif": iif, "Eval": evalTokens, "SafeHTML": safeHTML, - "HTMLFormat": htmlutil.HTMLFormat, + "HTMLFormat": htmlFormat, "HTMLEscape": htmlEscape, "QueryEscape": queryEscape, "QueryBuild": QueryBuild, @@ -131,15 +131,9 @@ func NewFuncMap() template.FuncMap { "EnableTimetracking": func() bool { return setting.Service.EnableTimetracking }, - "DisableGitHooks": func() bool { - return setting.DisableGitHooks - }, "DisableWebhooks": func() bool { return setting.DisableWebhooks }, - "DisableImportLocal": func() bool { - return !setting.ImportLocalPaths - }, "UserThemeName": userThemeName, "NotificationSettings": func() map[string]any { return map[string]any{ @@ -213,6 +207,20 @@ func htmlEscape(s any) template.HTML { panic(fmt.Sprintf("unexpected type %T", s)) } +func htmlFormat(s any, args ...any) template.HTML { + if len(args) == 0 { + // to prevent developers from calling "HTMLFormat $userInput" by mistake which will lead to XSS + panic("missing arguments for HTMLFormat") + } + switch v := s.(type) { + case string: + return htmlutil.HTMLFormat(template.HTML(v), args...) + case template.HTML: + return htmlutil.HTMLFormat(v, args...) + } + panic(fmt.Sprintf("unexpected type %T", s)) +} + func jsEscapeSafe(s string) template.HTML { return template.HTML(template.JSEscapeString(s)) } @@ -264,22 +272,42 @@ func userThemeName(user *user_model.User) string { return setting.UI.DefaultTheme } +func isQueryParamEmpty(v any) bool { + return v == nil || v == false || v == 0 || v == int64(0) || v == "" +} + // QueryBuild builds a query string from a list of key-value pairs. -// It omits the nil and empty strings, but it doesn't omit other zero values, -// because the zero value of number types may have a meaning. +// It omits the nil, false, zero int/int64 and empty string values, +// because they are default empty values for "ctx.FormXxx" calls. +// If 0 or false need to be included, use string values: "0" and "false". +// Build rules: +// * Even parameters: always build as query string: a=b&c=d +// * Odd parameters: +// * * {"/anything", param-pairs...} => "/?param-paris" +// * * {"anything?old-params", new-param-pairs...} => "anything?old-params&new-param-paris" +// * * Otherwise: {"old¶ms", new-param-pairs...} => "old¶ms&new-param-paris" +// * * Other behaviors are undefined yet. func QueryBuild(a ...any) template.URL { - var s string + var reqPath, s string + hasTrailingSep := false if len(a)%2 == 1 { if v, ok := a[0].(string); ok { - if v == "" || (v[0] != '?' && v[0] != '&') { - panic("QueryBuild: invalid argument") - } s = v } else if v, ok := a[0].(template.URL); ok { s = string(v) } else { panic("QueryBuild: invalid argument") } + hasTrailingSep = s != "&" && strings.HasSuffix(s, "&") + if strings.HasPrefix(s, "/") || strings.Contains(s, "?") { + if s1, s2, ok := strings.Cut(s, "?"); ok { + reqPath = s1 + "?" + s = s2 + } else { + reqPath += s + "?" + s = "" + } + } } for i := len(a) % 2; i < len(a); i += 2 { k, ok := a[i].(string) @@ -290,19 +318,16 @@ func QueryBuild(a ...any) template.URL { if va, ok := a[i+1].(string); ok { v = va } else if a[i+1] != nil { - v = fmt.Sprint(a[i+1]) + if !isQueryParamEmpty(a[i+1]) { + v = fmt.Sprint(a[i+1]) + } } // pos1 to pos2 is the "k=v&" part, "&" is optional pos1 := strings.Index(s, "&"+k+"=") if pos1 != -1 { pos1++ - } else { - pos1 = strings.Index(s, "?"+k+"=") - if pos1 != -1 { - pos1++ - } else if strings.HasPrefix(s, k+"=") { - pos1 = 0 - } + } else if strings.HasPrefix(s, k+"=") { + pos1 = 0 } pos2 := len(s) if pos1 == -1 { @@ -315,7 +340,7 @@ func QueryBuild(a ...any) template.URL { } if v != "" { sep := "" - hasPrefixSep := pos1 == 0 || (pos1 <= len(s) && (s[pos1-1] == '?' || s[pos1-1] == '&')) + hasPrefixSep := pos1 == 0 || (pos1 <= len(s) && s[pos1-1] == '&') if !hasPrefixSep { sep = "&" } @@ -324,14 +349,25 @@ func QueryBuild(a ...any) template.URL { s = s[:pos1] + s[pos2:] } } - if s != "" && s != "&" && s[len(s)-1] == '&' { + if s != "" && s[len(s)-1] == '&' && !hasTrailingSep { s = s[:len(s)-1] } + if reqPath != "" { + if s == "" { + s = reqPath + if s != "?" { + s = s[:len(s)-1] + } + } else { + if s[0] == '&' { + s = s[1:] + } + s = reqPath + s + } + } return template.URL(s) } func panicIfDevOrTesting() { - if !setting.IsProd || setting.IsInTesting { - panic("legacy template functions are for backward compatibility only, do not use them in new code") - } + setting.PanicInDevOrTesting("legacy template functions are for backward compatibility only, do not use them in new code") } |