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.

markdown.go 6.2KB

10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212
  1. // Copyright 2014 The Gogs Authors. All rights reserved.
  2. // Copyright 2018 The Gitea Authors. All rights reserved.
  3. // Use of this source code is governed by a MIT-style
  4. // license that can be found in the LICENSE file.
  5. package markdown
  6. import (
  7. "bytes"
  8. "io"
  9. "strings"
  10. "code.gitea.io/gitea/modules/markup"
  11. "code.gitea.io/gitea/modules/setting"
  12. "code.gitea.io/gitea/modules/util"
  13. "github.com/russross/blackfriday/v2"
  14. )
  15. // Renderer is a extended version of underlying render object.
  16. type Renderer struct {
  17. blackfriday.Renderer
  18. URLPrefix string
  19. IsWiki bool
  20. }
  21. var byteMailto = []byte("mailto:")
  22. var htmlEscaper = [256][]byte{
  23. '&': []byte("&"),
  24. '<': []byte("&lt;"),
  25. '>': []byte("&gt;"),
  26. '"': []byte("&quot;"),
  27. }
  28. func escapeHTML(w io.Writer, s []byte) {
  29. var start, end int
  30. for end < len(s) {
  31. escSeq := htmlEscaper[s[end]]
  32. if escSeq != nil {
  33. _, _ = w.Write(s[start:end])
  34. _, _ = w.Write(escSeq)
  35. start = end + 1
  36. }
  37. end++
  38. }
  39. if start < len(s) && end <= len(s) {
  40. _, _ = w.Write(s[start:end])
  41. }
  42. }
  43. // RenderNode is a default renderer of a single node of a syntax tree. For
  44. // block nodes it will be called twice: first time with entering=true, second
  45. // time with entering=false, so that it could know when it's working on an open
  46. // tag and when on close. It writes the result to w.
  47. //
  48. // The return value is a way to tell the calling walker to adjust its walk
  49. // pattern: e.g. it can terminate the traversal by returning Terminate. Or it
  50. // can ask the walker to skip a subtree of this node by returning SkipChildren.
  51. // The typical behavior is to return GoToNext, which asks for the usual
  52. // traversal to the next node.
  53. func (r *Renderer) RenderNode(w io.Writer, node *blackfriday.Node, entering bool) blackfriday.WalkStatus {
  54. switch node.Type {
  55. case blackfriday.Image:
  56. prefix := r.URLPrefix
  57. if r.IsWiki {
  58. prefix = util.URLJoin(prefix, "wiki", "raw")
  59. }
  60. prefix = strings.Replace(prefix, "/src/", "/media/", 1)
  61. link := node.LinkData.Destination
  62. if len(link) > 0 && !markup.IsLink(link) {
  63. lnk := string(link)
  64. lnk = util.URLJoin(prefix, lnk)
  65. lnk = strings.Replace(lnk, " ", "+", -1)
  66. link = []byte(lnk)
  67. }
  68. node.LinkData.Destination = link
  69. // Render link around image only if parent is not link already
  70. if node.Parent != nil && node.Parent.Type != blackfriday.Link {
  71. if entering {
  72. _, _ = w.Write([]byte(`<a href="`))
  73. escapeHTML(w, link)
  74. _, _ = w.Write([]byte(`">`))
  75. return r.Renderer.RenderNode(w, node, entering)
  76. }
  77. s := r.Renderer.RenderNode(w, node, entering)
  78. _, _ = w.Write([]byte(`</a>`))
  79. return s
  80. }
  81. return r.Renderer.RenderNode(w, node, entering)
  82. case blackfriday.Link:
  83. // special case: this is not a link, a hash link or a mailto:, so it's a
  84. // relative URL
  85. link := node.LinkData.Destination
  86. if len(link) > 0 && !markup.IsLink(link) &&
  87. link[0] != '#' && !bytes.HasPrefix(link, byteMailto) &&
  88. node.LinkData.Footnote == nil {
  89. lnk := string(link)
  90. if r.IsWiki {
  91. lnk = util.URLJoin("wiki", lnk)
  92. }
  93. link = []byte(util.URLJoin(r.URLPrefix, lnk))
  94. }
  95. node.LinkData.Destination = link
  96. return r.Renderer.RenderNode(w, node, entering)
  97. case blackfriday.Text:
  98. isListItem := false
  99. for n := node.Parent; n != nil; n = n.Parent {
  100. if n.Type == blackfriday.Item {
  101. isListItem = true
  102. break
  103. }
  104. }
  105. if isListItem {
  106. text := node.Literal
  107. switch {
  108. case bytes.HasPrefix(text, []byte("[ ] ")):
  109. _, _ = w.Write([]byte(`<span class="ui fitted disabled checkbox"><input type="checkbox" disabled="disabled" /><label /></span>`))
  110. text = text[3:]
  111. case bytes.HasPrefix(text, []byte("[x] ")):
  112. _, _ = w.Write([]byte(`<span class="ui checked fitted disabled checkbox"><input type="checkbox" checked="" disabled="disabled" /><label /></span>`))
  113. text = text[3:]
  114. }
  115. node.Literal = text
  116. }
  117. }
  118. return r.Renderer.RenderNode(w, node, entering)
  119. }
  120. const (
  121. blackfridayExtensions = 0 |
  122. blackfriday.NoIntraEmphasis |
  123. blackfriday.Tables |
  124. blackfriday.FencedCode |
  125. blackfriday.Strikethrough |
  126. blackfriday.NoEmptyLineBeforeBlock |
  127. blackfriday.DefinitionLists |
  128. blackfriday.Footnotes |
  129. blackfriday.HeadingIDs |
  130. blackfriday.AutoHeadingIDs
  131. blackfridayHTMLFlags = 0 |
  132. blackfriday.Smartypants
  133. )
  134. // RenderRaw renders Markdown to HTML without handling special links.
  135. func RenderRaw(body []byte, urlPrefix string, wikiMarkdown bool) []byte {
  136. renderer := &Renderer{
  137. Renderer: blackfriday.NewHTMLRenderer(blackfriday.HTMLRendererParameters{
  138. Flags: blackfridayHTMLFlags,
  139. }),
  140. URLPrefix: urlPrefix,
  141. IsWiki: wikiMarkdown,
  142. }
  143. exts := blackfridayExtensions
  144. if setting.Markdown.EnableHardLineBreak {
  145. exts |= blackfriday.HardLineBreak
  146. }
  147. // Need to normalize EOL to UNIX LF to have consistent results in rendering
  148. body = blackfriday.Run(util.NormalizeEOL(body), blackfriday.WithRenderer(renderer), blackfriday.WithExtensions(exts))
  149. return markup.SanitizeBytes(body)
  150. }
  151. var (
  152. // MarkupName describes markup's name
  153. MarkupName = "markdown"
  154. )
  155. func init() {
  156. markup.RegisterParser(Parser{})
  157. }
  158. // Parser implements markup.Parser
  159. type Parser struct {
  160. }
  161. // Name implements markup.Parser
  162. func (Parser) Name() string {
  163. return MarkupName
  164. }
  165. // Extensions implements markup.Parser
  166. func (Parser) Extensions() []string {
  167. return setting.Markdown.FileExtensions
  168. }
  169. // Render implements markup.Parser
  170. func (Parser) Render(rawBytes []byte, urlPrefix string, metas map[string]string, isWiki bool) []byte {
  171. return RenderRaw(rawBytes, urlPrefix, isWiki)
  172. }
  173. // Render renders Markdown to HTML with all specific handling stuff.
  174. func Render(rawBytes []byte, urlPrefix string, metas map[string]string) []byte {
  175. return markup.Render("a.md", rawBytes, urlPrefix, metas)
  176. }
  177. // RenderString renders Markdown to HTML with special links and returns string type.
  178. func RenderString(raw, urlPrefix string, metas map[string]string) string {
  179. return markup.RenderString("a.md", raw, urlPrefix, metas)
  180. }
  181. // RenderWiki renders markdown wiki page to HTML and return HTML string
  182. func RenderWiki(rawBytes []byte, urlPrefix string, metas map[string]string) string {
  183. return markup.RenderWiki("a.md", rawBytes, urlPrefix, metas)
  184. }
  185. // IsMarkdownFile reports whether name looks like a Markdown file
  186. // based on its extension.
  187. func IsMarkdownFile(name string) bool {
  188. return markup.IsMarkupFile(name, MarkupName)
  189. }