Some checks failed
cache-seeder / prune (push) Has been skipped
cache-seeder / frontend (push) Failing after 3s
cache-seeder / python (push) Failing after 3s
cache-seeder / gobuild (push) Failing after 4s
cache-seeder / lint (bindata, lint-backend) (push) Failing after 3s
giteabot backport / giteabot (push) Has been skipped
giteabot / giteabot (push) Has been skipped
cache-prune / prune (push) Has been skipped
release-nightly-snapcraft / build-and-publish (ubuntu-24.04) (push) Has been cancelled
release-nightly-snapcraft / build-and-publish (ubuntu-24.04-arm) (push) Has been cancelled
release-nightly / nightly-binary (push) Has been cancelled
release-nightly / nightly-container (push) Has been cancelled
cron-renovate / cron-renovate (push) Has been skipped
85 lines
2.9 KiB
Go
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
|
|
}
|
|
}
|