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

linkify.go 3.6 kB

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