Files
Gitea/modules/markup/common/common.go
T
wxiaoguang 938cde959e refactor: markup render (#38864)
1. add missing CSP header to api & web render endpoints.
2. make jupyter render skip post-processors, nothing to process
3. make ShortLinkProcessor correctly validate URL schemes and respect
the CustomURLSchemes setting
2026-08-11 18:01:02 +02:00

85 lines
2.9 KiB
Go

// Copyright 2026 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package common
import (
"regexp"
"slices"
"strings"
"sync"
"gitea.dev/modules/container"
"gitea.dev/modules/log"
"gitea.dev/modules/util"
"mvdan.cc/xurls/v2"
)
type globalVarsType struct {
schemeRegexp *regexp.Regexp // extract the scheme part from a link
wwwURLRegexp *regexp.Regexp // matching "www.{any-site}/{any-path}" pattern
LinkifyRegex *regexp.Regexp // fast matching a URL link (powered by "xurls" package with custom schemes), no any extra validation.
allowedSchemes []string // nil means "allow all" (but disable the unsafe ones)
DisallowedSchemes []string
}
const regexpScheme = `[a-zA-Z][-+.a-zA-Z0-9]*`
var GlobalVars = sync.OnceValue(func() *globalVarsType {
v := &globalVarsType{}
v.schemeRegexp = regexp.MustCompile(`^` + regexpScheme + `:`)
v.wwwURLRegexp = regexp.MustCompile(`^www\.[-a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,6}((?:/|[#?])[-a-zA-Z0-9@:%_\+.~#!?&//=\(\);,'">\^{}\[\]` + "`" + `]*)?`)
v.LinkifyRegex, _ = xurls.StrictMatchingScheme("https?://")
v.allowedSchemes = []string{"http", "https"}
v.DisallowedSchemes = []string{"data", "javascript", "vbscript"}
return v
})
type CheckLinkURLSchemeResult struct {
HasScheme, AllowToLinkify bool
}
func CheckLinkURLScheme(link string) CheckLinkURLSchemeResult {
vars := GlobalVars()
m := vars.schemeRegexp.FindStringSubmatch(link)
if m == nil {
return CheckLinkURLSchemeResult{AllowToLinkify: true} // relative link is always valid
}
urlScheme := strings.ToLower(m[0])
urlScheme = urlScheme[0 : len(urlScheme)-1] // remove the trailing ":"
allowed := len(vars.allowedSchemes) == 0 || slices.Contains(vars.allowedSchemes, urlScheme)
disabled := slices.Contains(vars.DisallowedSchemes, urlScheme)
return CheckLinkURLSchemeResult{HasScheme: true, AllowToLinkify: allowed && !disabled}
}
func InitLinkURLSchemes(customSchemes []string) {
validScheme := regexp.MustCompile(`^` + regexpScheme + `$`)
schemes := container.Set[string]{}
for _, scheme := range customSchemes {
schemeLower := strings.ToLower(scheme)
if !validScheme.MatchString(schemeLower) {
log.Error("Invalid custom URL scheme %q", scheme)
continue
}
schemes.Add(schemeLower)
}
// HINT: CUSTOM-URL-SCHEMES-ALLOW: setting custom means also allow them besides http/https, no custom means "allow all"
if len(schemes) > 0 {
schemes.AddMultiple("http", "https")
linkifyRegexps := make([]string, 0, len(schemes))
for _, s := range schemes.Values() {
s += util.Iif(slices.Contains(xurls.SchemesNoAuthority, s), ":", "://")
linkifyRegexps = append(linkifyRegexps, regexp.QuoteMeta(s))
}
GlobalVars().LinkifyRegex, _ = xurls.StrictMatchingScheme(strings.Join(linkifyRegexps, "|"))
GlobalVars().allowedSchemes = schemes.Values()
} else {
GlobalVars().LinkifyRegex, _ = xurls.StrictMatchingScheme("https?://") // only auto-linkify http and https
GlobalVars().allowedSchemes = nil
}
}