gitea源码

linkify.go 3.8KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167
  1. // Copyright 2019 Yusuke Inuzuka
  2. // Copyright 2019 The Gitea Authors. All rights reserved.
  3. // SPDX-License-Identifier: MIT
  4. // Most of this file is a subtly changed version of github.com/yuin/goldmark/extension/linkify.go
  5. package common
  6. import (
  7. "bytes"
  8. "regexp"
  9. "sync"
  10. "github.com/yuin/goldmark"
  11. "github.com/yuin/goldmark/ast"
  12. "github.com/yuin/goldmark/parser"
  13. "github.com/yuin/goldmark/text"
  14. "github.com/yuin/goldmark/util"
  15. "mvdan.cc/xurls/v2"
  16. )
  17. type GlobalVarsType struct {
  18. wwwURLRegxp *regexp.Regexp
  19. LinkRegex *regexp.Regexp // fast matching a URL link, no any extra validation.
  20. }
  21. var GlobalVars = sync.OnceValue(func() *GlobalVarsType {
  22. v := &GlobalVarsType{}
  23. v.wwwURLRegxp = regexp.MustCompile(`^www\.[-a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,6}((?:/|[#?])[-a-zA-Z0-9@:%_\+.~#!?&//=\(\);,'">\^{}\[\]` + "`" + `]*)?`)
  24. v.LinkRegex, _ = xurls.StrictMatchingScheme("https?://")
  25. return v
  26. })
  27. type linkifyParser struct{}
  28. var defaultLinkifyParser = &linkifyParser{}
  29. // NewLinkifyParser return a new InlineParser can parse
  30. // text that seems like a URL.
  31. func NewLinkifyParser() parser.InlineParser {
  32. return defaultLinkifyParser
  33. }
  34. func (s *linkifyParser) Trigger() []byte {
  35. // ' ' indicates any white spaces and a line head
  36. return []byte{' ', '*', '_', '~', '('}
  37. }
  38. var (
  39. protoHTTP = []byte("http:")
  40. protoHTTPS = []byte("https:")
  41. protoFTP = []byte("ftp:")
  42. domainWWW = []byte("www.")
  43. )
  44. func (s *linkifyParser) Parse(parent ast.Node, block text.Reader, pc parser.Context) ast.Node {
  45. if pc.IsInLinkLabel() {
  46. return nil
  47. }
  48. line, segment := block.PeekLine()
  49. consumes := 0
  50. start := segment.Start
  51. c := line[0]
  52. // advance if current position is not a line head.
  53. if c == ' ' || c == '*' || c == '_' || c == '~' || c == '(' {
  54. consumes++
  55. start++
  56. line = line[1:]
  57. }
  58. var m []int
  59. var protocol []byte
  60. typ := ast.AutoLinkURL
  61. if bytes.HasPrefix(line, protoHTTP) || bytes.HasPrefix(line, protoHTTPS) || bytes.HasPrefix(line, protoFTP) {
  62. m = GlobalVars().LinkRegex.FindSubmatchIndex(line)
  63. }
  64. if m == nil && bytes.HasPrefix(line, domainWWW) {
  65. m = GlobalVars().wwwURLRegxp.FindSubmatchIndex(line)
  66. protocol = []byte("http")
  67. }
  68. if m != nil {
  69. lastChar := line[m[1]-1]
  70. if lastChar == '.' {
  71. m[1]--
  72. } else if lastChar == ')' {
  73. closing := 0
  74. for i := m[1] - 1; i >= m[0]; i-- {
  75. switch line[i] {
  76. case ')':
  77. closing++
  78. case '(':
  79. closing--
  80. }
  81. }
  82. if closing > 0 {
  83. m[1] -= closing
  84. }
  85. } else if lastChar == ';' {
  86. i := m[1] - 2
  87. for ; i >= m[0]; i-- {
  88. if util.IsAlphaNumeric(line[i]) {
  89. continue
  90. }
  91. break
  92. }
  93. if i != m[1]-2 {
  94. if line[i] == '&' {
  95. m[1] -= m[1] - i
  96. }
  97. }
  98. }
  99. }
  100. if m == nil {
  101. if len(line) > 0 && util.IsPunct(line[0]) {
  102. return nil
  103. }
  104. typ = ast.AutoLinkEmail
  105. stop := util.FindEmailIndex(line)
  106. if stop < 0 {
  107. return nil
  108. }
  109. at := bytes.IndexByte(line, '@')
  110. m = []int{0, stop, at, stop - 1}
  111. if bytes.IndexByte(line[m[2]:m[3]], '.') < 0 {
  112. return nil
  113. }
  114. lastChar := line[m[1]-1]
  115. if lastChar == '.' {
  116. m[1]--
  117. }
  118. if m[1] < len(line) {
  119. nextChar := line[m[1]]
  120. if nextChar == '-' || nextChar == '_' {
  121. return nil
  122. }
  123. }
  124. }
  125. if consumes != 0 {
  126. s := segment.WithStop(segment.Start + 1)
  127. ast.MergeOrAppendTextSegment(parent, s)
  128. }
  129. consumes += m[1]
  130. block.Advance(consumes)
  131. n := ast.NewTextSegment(text.NewSegment(start, start+m[1]))
  132. link := ast.NewAutoLink(typ, n)
  133. link.Protocol = protocol
  134. return link
  135. }
  136. func (s *linkifyParser) CloseBlock(parent ast.Node, pc parser.Context) {
  137. // nothing to do
  138. }
  139. type linkify struct{}
  140. // Linkify is an extension that allow you to parse text that seems like a URL.
  141. var Linkify = &linkify{}
  142. func (e *linkify) Extend(m goldmark.Markdown) {
  143. m.Parser().AddOptions(
  144. parser.WithInlineParsers(
  145. util.Prioritized(NewLinkifyParser(), 999),
  146. ),
  147. )
  148. }