| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167 |
- // Copyright 2019 Yusuke Inuzuka
- // Copyright 2019 The Gitea Authors. All rights reserved.
- // SPDX-License-Identifier: MIT
-
- // Most of this file is a subtly changed version of github.com/yuin/goldmark/extension/linkify.go
-
- package common
-
- import (
- "bytes"
- "regexp"
- "sync"
-
- "github.com/yuin/goldmark"
- "github.com/yuin/goldmark/ast"
- "github.com/yuin/goldmark/parser"
- "github.com/yuin/goldmark/text"
- "github.com/yuin/goldmark/util"
- "mvdan.cc/xurls/v2"
- )
-
- type GlobalVarsType struct {
- wwwURLRegxp *regexp.Regexp
- LinkRegex *regexp.Regexp // fast matching a URL link, no any extra validation.
- }
-
- var GlobalVars = sync.OnceValue(func() *GlobalVarsType {
- v := &GlobalVarsType{}
- v.wwwURLRegxp = regexp.MustCompile(`^www\.[-a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,6}((?:/|[#?])[-a-zA-Z0-9@:%_\+.~#!?&//=\(\);,'">\^{}\[\]` + "`" + `]*)?`)
- v.LinkRegex, _ = xurls.StrictMatchingScheme("https?://")
- return v
- })
-
- type linkifyParser struct{}
-
- var defaultLinkifyParser = &linkifyParser{}
-
- // NewLinkifyParser return a new InlineParser can parse
- // text that seems like a URL.
- func NewLinkifyParser() parser.InlineParser {
- return defaultLinkifyParser
- }
-
- func (s *linkifyParser) Trigger() []byte {
- // ' ' indicates any white spaces and a line head
- return []byte{' ', '*', '_', '~', '('}
- }
-
- var (
- protoHTTP = []byte("http:")
- protoHTTPS = []byte("https:")
- protoFTP = []byte("ftp:")
- domainWWW = []byte("www.")
- )
-
- func (s *linkifyParser) Parse(parent ast.Node, block text.Reader, pc parser.Context) ast.Node {
- if pc.IsInLinkLabel() {
- return nil
- }
- line, segment := block.PeekLine()
- consumes := 0
- start := segment.Start
- c := line[0]
- // advance if current position is not a line head.
- if c == ' ' || c == '*' || c == '_' || c == '~' || c == '(' {
- consumes++
- start++
- line = line[1:]
- }
-
- var m []int
- var protocol []byte
- typ := ast.AutoLinkURL
- if bytes.HasPrefix(line, protoHTTP) || bytes.HasPrefix(line, protoHTTPS) || bytes.HasPrefix(line, protoFTP) {
- m = GlobalVars().LinkRegex.FindSubmatchIndex(line)
- }
- if m == nil && bytes.HasPrefix(line, domainWWW) {
- m = GlobalVars().wwwURLRegxp.FindSubmatchIndex(line)
- protocol = []byte("http")
- }
- if m != nil {
- lastChar := line[m[1]-1]
- if lastChar == '.' {
- m[1]--
- } else if lastChar == ')' {
- closing := 0
- for i := m[1] - 1; i >= m[0]; i-- {
- switch line[i] {
- case ')':
- closing++
- case '(':
- closing--
- }
- }
- if closing > 0 {
- m[1] -= closing
- }
- } else if lastChar == ';' {
- i := m[1] - 2
- for ; i >= m[0]; i-- {
- if util.IsAlphaNumeric(line[i]) {
- continue
- }
- break
- }
- if i != m[1]-2 {
- if line[i] == '&' {
- m[1] -= m[1] - i
- }
- }
- }
- }
- if m == nil {
- if len(line) > 0 && util.IsPunct(line[0]) {
- return nil
- }
- typ = ast.AutoLinkEmail
- stop := util.FindEmailIndex(line)
- if stop < 0 {
- return nil
- }
- at := bytes.IndexByte(line, '@')
- m = []int{0, stop, at, stop - 1}
- if bytes.IndexByte(line[m[2]:m[3]], '.') < 0 {
- return nil
- }
- lastChar := line[m[1]-1]
- if lastChar == '.' {
- m[1]--
- }
- if m[1] < len(line) {
- nextChar := line[m[1]]
- if nextChar == '-' || nextChar == '_' {
- return nil
- }
- }
- }
-
- if consumes != 0 {
- s := segment.WithStop(segment.Start + 1)
- ast.MergeOrAppendTextSegment(parent, s)
- }
- consumes += m[1]
- block.Advance(consumes)
- n := ast.NewTextSegment(text.NewSegment(start, start+m[1]))
- link := ast.NewAutoLink(typ, n)
- link.Protocol = protocol
- return link
- }
-
- func (s *linkifyParser) CloseBlock(parent ast.Node, pc parser.Context) {
- // nothing to do
- }
-
- type linkify struct{}
-
- // Linkify is an extension that allow you to parse text that seems like a URL.
- var Linkify = &linkify{}
-
- func (e *linkify) Extend(m goldmark.Markdown) {
- m.Parser().AddOptions(
- parser.WithInlineParsers(
- util.Prioritized(NewLinkifyParser(), 999),
- ),
- )
- }
|