mirror of https://github.com/go-gitea/gitea.git
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.
216 lines
5.8 KiB
216 lines
5.8 KiB
// Copyright 2024 The Gitea Authors. All rights reserved. |
|
// SPDX-License-Identifier: MIT |
|
|
|
package markup |
|
|
|
import ( |
|
"net/url" |
|
"path" |
|
"strings" |
|
|
|
"code.gitea.io/gitea/modules/markup/common" |
|
"code.gitea.io/gitea/modules/util" |
|
|
|
"golang.org/x/net/html" |
|
"golang.org/x/net/html/atom" |
|
) |
|
|
|
func shortLinkProcessor(ctx *RenderContext, node *html.Node) { |
|
next := node.NextSibling |
|
for node != nil && node != next { |
|
m := globalVars().shortLinkPattern.FindStringSubmatchIndex(node.Data) |
|
if m == nil { |
|
return |
|
} |
|
|
|
content := node.Data[m[2]:m[3]] |
|
tail := node.Data[m[4]:m[5]] |
|
props := make(map[string]string) |
|
|
|
// MediaWiki uses [[link|text]], while GitHub uses [[text|link]] |
|
// It makes page handling terrible, but we prefer GitHub syntax |
|
// And fall back to MediaWiki only when it is obvious from the look |
|
// Of text and link contents |
|
sl := strings.SplitSeq(content, "|") |
|
for v := range sl { |
|
if found := strings.Contains(v, "="); !found { |
|
// There is no equal in this argument; this is a mandatory arg |
|
if props["name"] == "" { |
|
if IsFullURLString(v) { |
|
// If we clearly see it is a link, we save it so |
|
|
|
// But first we need to ensure, that if both mandatory args provided |
|
// look like links, we stick to GitHub syntax |
|
if props["link"] != "" { |
|
props["name"] = props["link"] |
|
} |
|
|
|
props["link"] = strings.TrimSpace(v) |
|
} else { |
|
props["name"] = v |
|
} |
|
} else { |
|
props["link"] = strings.TrimSpace(v) |
|
} |
|
} else { |
|
// There is an equal; optional argument. |
|
|
|
before, after, _ := strings.Cut(v, "=") |
|
key, val := before, html.UnescapeString(after) |
|
|
|
// When parsing HTML, x/net/html will change all quotes which are |
|
// not used for syntax into UTF-8 quotes. So checking val[0] won't |
|
// be enough, since that only checks a single byte. |
|
if len(val) > 1 { |
|
if (strings.HasPrefix(val, "“") && strings.HasSuffix(val, "”")) || |
|
(strings.HasPrefix(val, "‘") && strings.HasSuffix(val, "’")) { |
|
const lenQuote = len("‘") |
|
val = val[lenQuote : len(val)-lenQuote] |
|
} else if (strings.HasPrefix(val, "\"") && strings.HasSuffix(val, "\"")) || |
|
(strings.HasPrefix(val, "'") && strings.HasSuffix(val, "'")) { |
|
val = val[1 : len(val)-1] |
|
} else if strings.HasPrefix(val, "'") && strings.HasSuffix(val, "’") { |
|
const lenQuote = len("‘") |
|
val = val[1 : len(val)-lenQuote] |
|
} |
|
} |
|
props[key] = val |
|
} |
|
} |
|
|
|
var name, link string |
|
if props["link"] != "" { |
|
link = props["link"] |
|
} else if props["name"] != "" { |
|
link = props["name"] |
|
} |
|
if props["title"] != "" { |
|
name = props["title"] |
|
} else if props["name"] != "" { |
|
name = props["name"] |
|
} else { |
|
name = link |
|
} |
|
|
|
name += tail |
|
image := false |
|
ext := path.Ext(link) |
|
switch ext { |
|
// fast path: empty string, ignore |
|
case "": |
|
// leave image as false |
|
case ".jpg", ".jpeg", ".png", ".tif", ".tiff", ".webp", ".gif", ".bmp", ".ico", ".svg": |
|
image = true |
|
} |
|
|
|
childNode := &html.Node{} |
|
linkNode := &html.Node{ |
|
FirstChild: childNode, |
|
LastChild: childNode, |
|
Type: html.ElementNode, |
|
Data: "a", |
|
DataAtom: atom.A, |
|
} |
|
childNode.Parent = linkNode |
|
absoluteLink := IsFullURLString(link) |
|
if !absoluteLink { |
|
if image { |
|
link = strings.ReplaceAll(link, " ", "+") |
|
} else { |
|
// the hacky wiki name encoding: space to "-" |
|
link = strings.ReplaceAll(link, " ", "-") // FIXME: it should support dashes in the link, eg: "the-dash-support.-" |
|
} |
|
if !strings.Contains(link, "/") { |
|
link = url.PathEscape(link) // FIXME: it doesn't seem right and it might cause double-escaping |
|
} |
|
} |
|
if image { |
|
title := props["title"] |
|
if title == "" { |
|
title = props["alt"] |
|
} |
|
if title == "" { |
|
title = path.Base(name) |
|
} |
|
alt := props["alt"] |
|
if alt == "" { |
|
alt = name |
|
} |
|
|
|
// make the childNode an image - if we can, we also place the alt |
|
childNode.Type = html.ElementNode |
|
childNode.Data = "img" |
|
childNode.DataAtom = atom.Img |
|
childNode.Attr = []html.Attribute{ |
|
{Key: "src", Val: link}, |
|
{Key: "title", Val: title}, |
|
{Key: "alt", Val: alt}, |
|
} |
|
if alt == "" { |
|
childNode.Attr = childNode.Attr[:2] |
|
} |
|
} else { |
|
childNode.Type = html.TextNode |
|
childNode.Data = name |
|
} |
|
linkNode.Attr = []html.Attribute{{Key: "href", Val: link}} |
|
replaceContent(node, m[0], m[1], linkNode) |
|
node = node.NextSibling.NextSibling |
|
} |
|
} |
|
|
|
// linkProcessor creates links for any HTTP or HTTPS URL not captured by |
|
// markdown. |
|
func linkProcessor(ctx *RenderContext, node *html.Node) { |
|
next := node.NextSibling |
|
for node != nil && node != next { |
|
m := common.GlobalVars().LinkRegex.FindStringIndex(node.Data) |
|
if m == nil { |
|
return |
|
} |
|
|
|
uri := node.Data[m[0]:m[1]] |
|
remaining := node.Data[m[1]:] |
|
if util.IsLikelyEllipsisLeftPart(remaining) { |
|
return |
|
} |
|
replaceContent(node, m[0], m[1], createLink(ctx, uri, uri, "" /*link*/)) |
|
node = node.NextSibling.NextSibling |
|
} |
|
} |
|
|
|
// descriptionLinkProcessor creates links for DescriptionHTML |
|
func descriptionLinkProcessor(ctx *RenderContext, node *html.Node) { |
|
next := node.NextSibling |
|
for node != nil && node != next { |
|
m := common.GlobalVars().LinkRegex.FindStringIndex(node.Data) |
|
if m == nil { |
|
return |
|
} |
|
|
|
uri := node.Data[m[0]:m[1]] |
|
replaceContent(node, m[0], m[1], createDescriptionLink(uri, uri)) |
|
node = node.NextSibling.NextSibling |
|
} |
|
} |
|
|
|
func createDescriptionLink(href, content string) *html.Node { |
|
textNode := &html.Node{ |
|
Type: html.TextNode, |
|
Data: content, |
|
} |
|
linkNode := &html.Node{ |
|
FirstChild: textNode, |
|
LastChild: textNode, |
|
Type: html.ElementNode, |
|
Data: "a", |
|
DataAtom: atom.A, |
|
Attr: []html.Attribute{ |
|
{Key: "href", Val: href}, |
|
{Key: "target", Val: "_blank"}, |
|
{Key: "rel", Val: "noopener noreferrer"}, |
|
}, |
|
} |
|
textNode.Parent = linkNode |
|
return linkNode |
|
}
|
|
|