|
- // Copyright 2019 Yusuke Inuzuka
- // Copyright 2019 The Gitea Authors. All rights reserved.
- // Use of this source code is governed by a MIT-style
- // license that can be found in the LICENSE file.
-
- // Most of this file is a subtly changed version of github.com/yuin/goldmark/extension/linkify.go
-
- package common
-
- import (
- "bytes"
- "regexp"
-
- "github.com/yuin/goldmark"
- "github.com/yuin/goldmark/ast"
- "github.com/yuin/goldmark/parser"
- "github.com/yuin/goldmark/text"
- "github.com/yuin/goldmark/util"
- )
-
- var wwwURLRegxp = regexp.MustCompile(`^www\.[-a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,6}((?:/|[#?])[-a-zA-Z0-9@:%_\+.~#!?&//=\(\);,'">\^{}\[\]` + "`" + `]*)?`)
-
- 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:")
- var protoHTTPS = []byte("https:")
- var protoFTP = []byte("ftp:")
- var 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
- var typ ast.AutoLinkType = ast.AutoLinkURL
- if bytes.HasPrefix(line, protoHTTP) || bytes.HasPrefix(line, protoHTTPS) || bytes.HasPrefix(line, protoFTP) {
- m = LinkRegex.FindSubmatchIndex(line)
- }
- if m == nil && bytes.HasPrefix(line, domainWWW) {
- m = 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-- {
- if line[i] == ')' {
- closing++
- } else if line[i] == '(' {
- 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 m == nil {
- 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),
- ),
- )
- }
|