You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

helper.go 8.1KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263
  1. // Copyright 2018 The Gitea Authors. All rights reserved.
  2. // Copyright 2014 The Gogs Authors. All rights reserved.
  3. // SPDX-License-Identifier: MIT
  4. package templates
  5. import (
  6. "fmt"
  7. "html"
  8. "html/template"
  9. "net/url"
  10. "slices"
  11. "strings"
  12. "time"
  13. user_model "code.gitea.io/gitea/models/user"
  14. "code.gitea.io/gitea/modules/base"
  15. "code.gitea.io/gitea/modules/markup"
  16. "code.gitea.io/gitea/modules/setting"
  17. "code.gitea.io/gitea/modules/svg"
  18. "code.gitea.io/gitea/modules/templates/eval"
  19. "code.gitea.io/gitea/modules/timeutil"
  20. "code.gitea.io/gitea/modules/util"
  21. "code.gitea.io/gitea/services/gitdiff"
  22. )
  23. // NewFuncMap returns functions for injecting to templates
  24. func NewFuncMap() template.FuncMap {
  25. return map[string]any{
  26. "ctx": func() any { return nil }, // template context function
  27. "DumpVar": dumpVar,
  28. // -----------------------------------------------------------------
  29. // html/template related functions
  30. "dict": dict, // it's lowercase because this name has been widely used. Our other functions should have uppercase names.
  31. "Iif": Iif,
  32. "Eval": Eval,
  33. "SafeHTML": SafeHTML,
  34. "HTMLFormat": HTMLFormat,
  35. "HTMLEscape": HTMLEscape,
  36. "QueryEscape": QueryEscape,
  37. "JSEscape": JSEscapeSafe,
  38. "SanitizeHTML": SanitizeHTML,
  39. "URLJoin": util.URLJoin,
  40. "DotEscape": DotEscape,
  41. "PathEscape": url.PathEscape,
  42. "PathEscapeSegments": util.PathEscapeSegments,
  43. // utils
  44. "StringUtils": NewStringUtils,
  45. "SliceUtils": NewSliceUtils,
  46. "JsonUtils": NewJsonUtils,
  47. // -----------------------------------------------------------------
  48. // svg / avatar / icon / color
  49. "svg": svg.RenderHTML,
  50. "EntryIcon": base.EntryIcon,
  51. "MigrationIcon": MigrationIcon,
  52. "ActionIcon": ActionIcon,
  53. "SortArrow": SortArrow,
  54. "ContrastColor": util.ContrastColor,
  55. // -----------------------------------------------------------------
  56. // time / number / format
  57. "FileSize": base.FileSize,
  58. "CountFmt": base.FormatNumberSI,
  59. "TimeSince": timeutil.TimeSince,
  60. "TimeSinceUnix": timeutil.TimeSinceUnix,
  61. "DateTime": timeutil.DateTime,
  62. "Sec2Time": util.SecToTime,
  63. "LoadTimes": func(startTime time.Time) string {
  64. return fmt.Sprint(time.Since(startTime).Nanoseconds()/1e6) + "ms"
  65. },
  66. // -----------------------------------------------------------------
  67. // setting
  68. "AppName": func() string {
  69. return setting.AppName
  70. },
  71. "AppSubUrl": func() string {
  72. return setting.AppSubURL
  73. },
  74. "AssetUrlPrefix": func() string {
  75. return setting.StaticURLPrefix + "/assets"
  76. },
  77. "AppUrl": func() string {
  78. // The usage of AppUrl should be avoided as much as possible,
  79. // because the AppURL(ROOT_URL) may not match user's visiting site and the ROOT_URL in app.ini may be incorrect.
  80. // And it's difficult for Gitea to guess absolute URL correctly with zero configuration,
  81. // because Gitea doesn't know whether the scheme is HTTP or HTTPS unless the reverse proxy could tell Gitea.
  82. return setting.AppURL
  83. },
  84. "AppVer": func() string {
  85. return setting.AppVer
  86. },
  87. "AppDomain": func() string { // documented in mail-templates.md
  88. return setting.Domain
  89. },
  90. "AssetVersion": func() string {
  91. return setting.AssetVersion
  92. },
  93. "DefaultShowFullName": func() bool {
  94. return setting.UI.DefaultShowFullName
  95. },
  96. "ShowFooterTemplateLoadTime": func() bool {
  97. return setting.Other.ShowFooterTemplateLoadTime
  98. },
  99. "ShowFooterPoweredBy": func() bool {
  100. return setting.Other.ShowFooterPoweredBy
  101. },
  102. "AllowedReactions": func() []string {
  103. return setting.UI.Reactions
  104. },
  105. "CustomEmojis": func() map[string]string {
  106. return setting.UI.CustomEmojisMap
  107. },
  108. "MetaAuthor": func() string {
  109. return setting.UI.Meta.Author
  110. },
  111. "MetaDescription": func() string {
  112. return setting.UI.Meta.Description
  113. },
  114. "MetaKeywords": func() string {
  115. return setting.UI.Meta.Keywords
  116. },
  117. "EnableTimetracking": func() bool {
  118. return setting.Service.EnableTimetracking
  119. },
  120. "DisableGitHooks": func() bool {
  121. return setting.DisableGitHooks
  122. },
  123. "DisableWebhooks": func() bool {
  124. return setting.DisableWebhooks
  125. },
  126. "DisableImportLocal": func() bool {
  127. return !setting.ImportLocalPaths
  128. },
  129. "ThemeName": func(user *user_model.User) string {
  130. if user == nil || user.Theme == "" {
  131. return setting.UI.DefaultTheme
  132. }
  133. return user.Theme
  134. },
  135. "NotificationSettings": func() map[string]any {
  136. return map[string]any{
  137. "MinTimeout": int(setting.UI.Notification.MinTimeout / time.Millisecond),
  138. "TimeoutStep": int(setting.UI.Notification.TimeoutStep / time.Millisecond),
  139. "MaxTimeout": int(setting.UI.Notification.MaxTimeout / time.Millisecond),
  140. "EventSourceUpdateTime": int(setting.UI.Notification.EventSourceUpdateTime / time.Millisecond),
  141. }
  142. },
  143. "MermaidMaxSourceCharacters": func() int {
  144. return setting.MermaidMaxSourceCharacters
  145. },
  146. // -----------------------------------------------------------------
  147. // render
  148. "RenderCommitMessage": RenderCommitMessage,
  149. "RenderCommitMessageLinkSubject": RenderCommitMessageLinkSubject,
  150. "RenderCommitBody": RenderCommitBody,
  151. "RenderCodeBlock": RenderCodeBlock,
  152. "RenderIssueTitle": RenderIssueTitle,
  153. "RenderEmoji": RenderEmoji,
  154. "ReactionToEmoji": ReactionToEmoji,
  155. "RenderMarkdownToHtml": RenderMarkdownToHtml,
  156. "RenderLabel": RenderLabel,
  157. "RenderLabels": RenderLabels,
  158. // -----------------------------------------------------------------
  159. // misc
  160. "ShortSha": base.ShortSha,
  161. "ActionContent2Commits": ActionContent2Commits,
  162. "IsMultilineCommitMessage": IsMultilineCommitMessage,
  163. "CommentMustAsDiff": gitdiff.CommentMustAsDiff,
  164. "MirrorRemoteAddress": mirrorRemoteAddress,
  165. "FilenameIsImage": FilenameIsImage,
  166. "TabSizeClass": TabSizeClass,
  167. }
  168. }
  169. func HTMLFormat(s string, rawArgs ...any) template.HTML {
  170. args := slices.Clone(rawArgs)
  171. for i, v := range args {
  172. switch v := v.(type) {
  173. case nil, bool, int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, float32, float64, template.HTML:
  174. // for most basic types (including template.HTML which is safe), just do nothing and use it
  175. case string:
  176. args[i] = template.HTMLEscapeString(v)
  177. case fmt.Stringer:
  178. args[i] = template.HTMLEscapeString(v.String())
  179. default:
  180. args[i] = template.HTMLEscapeString(fmt.Sprint(v))
  181. }
  182. }
  183. return template.HTML(fmt.Sprintf(s, args...))
  184. }
  185. // SafeHTML render raw as HTML
  186. func SafeHTML(s any) template.HTML {
  187. switch v := s.(type) {
  188. case string:
  189. return template.HTML(v)
  190. case template.HTML:
  191. return v
  192. }
  193. panic(fmt.Sprintf("unexpected type %T", s))
  194. }
  195. // SanitizeHTML sanitizes the input by pre-defined markdown rules
  196. func SanitizeHTML(s string) template.HTML {
  197. return template.HTML(markup.Sanitize(s))
  198. }
  199. func HTMLEscape(s any) template.HTML {
  200. switch v := s.(type) {
  201. case string:
  202. return template.HTML(html.EscapeString(v))
  203. case template.HTML:
  204. return v
  205. }
  206. panic(fmt.Sprintf("unexpected type %T", s))
  207. }
  208. func JSEscapeSafe(s string) template.HTML {
  209. return template.HTML(template.JSEscapeString(s))
  210. }
  211. func QueryEscape(s string) template.URL {
  212. return template.URL(url.QueryEscape(s))
  213. }
  214. // DotEscape wraps a dots in names with ZWJ [U+200D] in order to prevent autolinkers from detecting these as urls
  215. func DotEscape(raw string) string {
  216. return strings.ReplaceAll(raw, ".", "\u200d.\u200d")
  217. }
  218. // Iif is an "inline-if", similar util.Iif[T] but templates need the non-generic version,
  219. // and it could be simply used as "{{Iif expr trueVal}}" (omit the falseVal).
  220. func Iif(condition bool, vals ...any) any {
  221. if condition {
  222. return vals[0]
  223. } else if len(vals) > 1 {
  224. return vals[1]
  225. }
  226. return nil
  227. }
  228. // Eval the expression and return the result, see the comment of eval.Expr for details.
  229. // To use this helper function in templates, pass each token as a separate parameter.
  230. //
  231. // {{ $int64 := Eval $var "+" 1 }}
  232. // {{ $float64 := Eval $var "+" 1.0 }}
  233. //
  234. // Golang's template supports comparable int types, so the int64 result can be used in later statements like {{if lt $int64 10}}
  235. func Eval(tokens ...any) (any, error) {
  236. n, err := eval.Expr(tokens...)
  237. return n.Value, err
  238. }