gitea源码

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232
  1. // Copyright 2023 The Gitea Authors. All rights reserved.
  2. // SPDX-License-Identifier: MIT
  3. package scopedtmpl
  4. import (
  5. "fmt"
  6. "html/template"
  7. "io"
  8. "maps"
  9. "reflect"
  10. "sync"
  11. texttemplate "text/template"
  12. "text/template/parse"
  13. "unsafe"
  14. )
  15. type TemplateExecutor interface {
  16. Execute(wr io.Writer, data any) error
  17. }
  18. type ScopedTemplate struct {
  19. all *template.Template
  20. parseFuncs template.FuncMap // this func map is only used for parsing templates
  21. frozen bool
  22. scopedMu sync.RWMutex
  23. scopedTemplateSets map[string]*scopedTemplateSet
  24. }
  25. func NewScopedTemplate() *ScopedTemplate {
  26. return &ScopedTemplate{
  27. all: template.New(""),
  28. parseFuncs: template.FuncMap{},
  29. scopedTemplateSets: map[string]*scopedTemplateSet{},
  30. }
  31. }
  32. func (t *ScopedTemplate) Funcs(funcMap template.FuncMap) {
  33. if t.frozen {
  34. panic("cannot add new functions to frozen template set")
  35. }
  36. t.all.Funcs(funcMap)
  37. maps.Copy(t.parseFuncs, funcMap)
  38. }
  39. func (t *ScopedTemplate) New(name string) *template.Template {
  40. if t.frozen {
  41. panic("cannot add new template to frozen template set")
  42. }
  43. return t.all.New(name)
  44. }
  45. func (t *ScopedTemplate) Freeze() {
  46. t.frozen = true
  47. // reset the exec func map, then `escapeTemplate` is safe to call `Execute` to do escaping
  48. m := template.FuncMap{}
  49. for k := range t.parseFuncs {
  50. m[k] = func(v ...any) any { return nil }
  51. }
  52. t.all.Funcs(m)
  53. }
  54. func (t *ScopedTemplate) Executor(name string, funcMap template.FuncMap) (TemplateExecutor, error) {
  55. t.scopedMu.RLock()
  56. scopedTmplSet, ok := t.scopedTemplateSets[name]
  57. t.scopedMu.RUnlock()
  58. if !ok {
  59. var err error
  60. t.scopedMu.Lock()
  61. if scopedTmplSet, ok = t.scopedTemplateSets[name]; !ok {
  62. if scopedTmplSet, err = newScopedTemplateSet(t.all, name); err == nil {
  63. t.scopedTemplateSets[name] = scopedTmplSet
  64. }
  65. }
  66. t.scopedMu.Unlock()
  67. if err != nil {
  68. return nil, err
  69. }
  70. }
  71. if scopedTmplSet == nil {
  72. return nil, fmt.Errorf("template %s not found", name)
  73. }
  74. return scopedTmplSet.newExecutor(funcMap), nil
  75. }
  76. type scopedTemplateSet struct {
  77. name string
  78. htmlTemplates map[string]*template.Template
  79. textTemplates map[string]*texttemplate.Template
  80. execFuncs map[string]reflect.Value
  81. }
  82. func escapeTemplate(t *template.Template) error {
  83. // force the Golang HTML template to complete the escaping work
  84. err := t.Execute(io.Discard, nil)
  85. if _, ok := err.(*template.Error); ok {
  86. return err
  87. }
  88. return nil
  89. }
  90. type htmlTemplate struct {
  91. _/*escapeErr*/ error
  92. text *texttemplate.Template
  93. }
  94. type textTemplateCommon struct {
  95. _/*tmpl*/ map[string]*template.Template
  96. _/*muTmpl*/ sync.RWMutex
  97. _/*option*/ struct {
  98. missingKey int
  99. }
  100. muFuncs sync.RWMutex
  101. _/*parseFuncs*/ texttemplate.FuncMap
  102. execFuncs map[string]reflect.Value
  103. }
  104. type textTemplate struct {
  105. _/*name*/ string
  106. *parse.Tree
  107. *textTemplateCommon
  108. _/*leftDelim*/ string
  109. _/*rightDelim*/ string
  110. }
  111. func ptr[T, P any](ptr *P) *T {
  112. // https://pkg.go.dev/unsafe#Pointer
  113. // (1) Conversion of a *T1 to Pointer to *T2.
  114. // Provided that T2 is no larger than T1 and that the two share an equivalent memory layout,
  115. // this conversion allows reinterpreting data of one type as data of another type.
  116. return (*T)(unsafe.Pointer(ptr))
  117. }
  118. func newScopedTemplateSet(all *template.Template, name string) (*scopedTemplateSet, error) {
  119. targetTmpl := all.Lookup(name)
  120. if targetTmpl == nil {
  121. return nil, fmt.Errorf("template %q not found", name)
  122. }
  123. if err := escapeTemplate(targetTmpl); err != nil {
  124. return nil, fmt.Errorf("template %q has an error when escaping: %w", name, err)
  125. }
  126. ts := &scopedTemplateSet{
  127. name: name,
  128. htmlTemplates: map[string]*template.Template{},
  129. textTemplates: map[string]*texttemplate.Template{},
  130. }
  131. htmlTmpl := ptr[htmlTemplate](all)
  132. textTmpl := htmlTmpl.text
  133. textTmplPtr := ptr[textTemplate](textTmpl)
  134. textTmplPtr.muFuncs.Lock()
  135. ts.execFuncs = map[string]reflect.Value{}
  136. maps.Copy(ts.execFuncs, textTmplPtr.execFuncs)
  137. textTmplPtr.muFuncs.Unlock()
  138. var collectTemplates func(nodes []parse.Node)
  139. var collectErr error // only need to collect the one error
  140. collectTemplates = func(nodes []parse.Node) {
  141. for _, node := range nodes {
  142. if node.Type() == parse.NodeTemplate {
  143. nodeTemplate := node.(*parse.TemplateNode)
  144. subName := nodeTemplate.Name
  145. if ts.htmlTemplates[subName] == nil {
  146. subTmpl := all.Lookup(subName)
  147. if subTmpl == nil {
  148. // HTML template will add some internal templates like "$delimDoubleQuote" into the text template
  149. ts.textTemplates[subName] = textTmpl.Lookup(subName)
  150. } else if subTmpl.Tree == nil || subTmpl.Tree.Root == nil {
  151. collectErr = fmt.Errorf("template %q has no tree, it's usually caused by broken templates", subName)
  152. } else {
  153. ts.htmlTemplates[subName] = subTmpl
  154. if err := escapeTemplate(subTmpl); err != nil {
  155. collectErr = fmt.Errorf("template %q has an error when escaping: %w", subName, err)
  156. return
  157. }
  158. collectTemplates(subTmpl.Tree.Root.Nodes)
  159. }
  160. }
  161. } else if node.Type() == parse.NodeList {
  162. nodeList := node.(*parse.ListNode)
  163. collectTemplates(nodeList.Nodes)
  164. } else if node.Type() == parse.NodeIf {
  165. nodeIf := node.(*parse.IfNode)
  166. collectTemplates(nodeIf.BranchNode.List.Nodes)
  167. if nodeIf.BranchNode.ElseList != nil {
  168. collectTemplates(nodeIf.BranchNode.ElseList.Nodes)
  169. }
  170. } else if node.Type() == parse.NodeRange {
  171. nodeRange := node.(*parse.RangeNode)
  172. collectTemplates(nodeRange.BranchNode.List.Nodes)
  173. if nodeRange.BranchNode.ElseList != nil {
  174. collectTemplates(nodeRange.BranchNode.ElseList.Nodes)
  175. }
  176. } else if node.Type() == parse.NodeWith {
  177. nodeWith := node.(*parse.WithNode)
  178. collectTemplates(nodeWith.BranchNode.List.Nodes)
  179. if nodeWith.BranchNode.ElseList != nil {
  180. collectTemplates(nodeWith.BranchNode.ElseList.Nodes)
  181. }
  182. }
  183. }
  184. }
  185. ts.htmlTemplates[name] = targetTmpl
  186. collectTemplates(targetTmpl.Tree.Root.Nodes)
  187. return ts, collectErr
  188. }
  189. func (ts *scopedTemplateSet) newExecutor(funcMap map[string]any) TemplateExecutor {
  190. tmpl := texttemplate.New("")
  191. tmplPtr := ptr[textTemplate](tmpl)
  192. tmplPtr.execFuncs = map[string]reflect.Value{}
  193. maps.Copy(tmplPtr.execFuncs, ts.execFuncs)
  194. if funcMap != nil {
  195. tmpl.Funcs(funcMap)
  196. }
  197. // after escapeTemplate, the html templates are also escaped text templates, so it could be added to the text template directly
  198. for _, t := range ts.htmlTemplates {
  199. _, _ = tmpl.AddParseTree(t.Name(), t.Tree)
  200. }
  201. for _, t := range ts.textTemplates {
  202. _, _ = tmpl.AddParseTree(t.Name(), t.Tree)
  203. }
  204. // now the text template has all necessary escaped templates, so we can safely execute, just like what the html template does
  205. return tmpl.Lookup(ts.name)
  206. }