forgejo/modules/validation/helpers.go
Panagiotis "Ivory" Vasilopoulos 81601eab85
Some checks are pending
/ release (push) Waiting to run
testing-integration / test-unit (push) Waiting to run
testing-integration / test-sqlite (push) Waiting to run
testing-integration / test-mariadb (v10.6) (push) Waiting to run
testing-integration / test-mariadb (v11.8) (push) Waiting to run
testing / backend-checks (push) Waiting to run
testing / frontend-checks (push) Waiting to run
testing / test-unit (push) Blocked by required conditions
testing / test-e2e (push) Blocked by required conditions
testing / test-remote-cacher (redis) (push) Blocked by required conditions
testing / test-remote-cacher (valkey) (push) Blocked by required conditions
testing / test-remote-cacher (garnet) (push) Blocked by required conditions
testing / test-remote-cacher (redict) (push) Blocked by required conditions
testing / test-mysql (push) Blocked by required conditions
testing / test-pgsql (push) Blocked by required conditions
testing / test-sqlite (push) Blocked by required conditions
testing / security-check (push) Blocked by required conditions
feat(activitypub): use structure @PreferredUsername@host.tld:port for actors (#9254)
This modifies usernames of ActivityPub accounts to use the @example@example.tld
format with an additional optional port component (e.g. @user@example.tld:42).
This allows accounts from ActivityPub servers with more relaxed username
requirements than those of Forgejo's to interact with Forgejo. Forgejo would
also follow a "de facto" standard of ActivityPub implementations.

By separating different information using @'s, we also gain future
opportunities to store more information about ActivityPub accounts internally,
so that we won't have to rely on e.g. the amount of dashes in a username as
my migration currently does.

Continuation of Aravinth's work: https://codeberg.org/forgejo/forgejo/pulls/4778

Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/9254
Reviewed-by: jerger <jerger@noreply.codeberg.org>
Reviewed-by: Ellen Εμιλία Άννα Zscheile <fogti@noreply.codeberg.org>
Reviewed-by: Gusted <gusted@noreply.codeberg.org>
Co-authored-by: Panagiotis "Ivory" Vasilopoulos <git@n0toose.net>
Co-committed-by: Panagiotis "Ivory" Vasilopoulos <git@n0toose.net>
2026-01-30 23:45:11 +01:00

139 lines
4.1 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Copyright 2018 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package validation
import (
"net"
"net/url"
"regexp"
"strings"
"forgejo.org/modules/setting"
)
var externalTrackerRegex = regexp.MustCompile(`({?)(?:user|repo|index)+?(}?)`)
func isLoopbackIP(ip string) bool {
return net.ParseIP(ip).IsLoopback()
}
// IsValidURL checks if URL is valid
func IsValidURL(uri string) bool {
if u, err := url.ParseRequestURI(uri); err != nil ||
(u.Scheme != "http" && u.Scheme != "https") ||
!validPort(portOnly(u.Host)) {
return false
}
return true
}
// IsValidSiteURL checks if URL is valid
func IsValidSiteURL(uri string) bool {
u, err := url.ParseRequestURI(uri)
if err != nil {
return false
}
if !validPort(portOnly(u.Host)) {
return false
}
for _, scheme := range setting.Service.ValidSiteURLSchemes {
if scheme == u.Scheme {
return true
}
}
return false
}
// IsAPIURL checks if URL is current Gitea instance API URL
func IsAPIURL(uri string) bool {
return strings.HasPrefix(strings.ToLower(uri), strings.ToLower(setting.AppURL+"api"))
}
// IsValidExternalURL checks if URL is valid external URL
func IsValidExternalURL(uri string) bool {
if !IsValidURL(uri) || IsAPIURL(uri) {
return false
}
u, err := url.ParseRequestURI(uri)
if err != nil {
return false
}
// Currently check only if not loopback IP is provided to keep compatibility
if isLoopbackIP(u.Hostname()) || strings.ToLower(u.Hostname()) == "localhost" {
return false
}
// TODO: Later it should be added to allow local network IP addresses
// only if allowed by special setting
return true
}
// IsValidReleaseAssetURL checks if the URL is valid for external release assets
func IsValidReleaseAssetURL(uri string) bool {
return IsValidURL(uri)
}
// IsValidExternalTrackerURLFormat checks if URL matches required syntax for external trackers
func IsValidExternalTrackerURLFormat(uri string) bool {
if !IsValidExternalURL(uri) {
return false
}
// check for typoed variables like /{index/ or /[repo}
for _, match := range externalTrackerRegex.FindAllStringSubmatch(uri, -1) {
if (match[1] == "{" || match[2] == "}") && (match[1] != "{" || match[2] != "}") {
return false
}
}
return true
}
var (
validUsernamePatternWithDots = regexp.MustCompile(`^[\da-zA-Z][-.\w]*$`)
validUsernamePatternWithoutDots = regexp.MustCompile(`^[\da-zA-Z][-\w]*$`)
// No consecutive or trailing non-alphanumeric chars, catches both cases
invalidUsernamePattern = regexp.MustCompile(`[-._]{2,}|[-._]$`)
// This is intended to accept any character, in any language, with accent symbols,
// as well as an arbitrary amount of subdomains and an optional port number defined
// through `:12345`.
//
// This is intended to cover username cases from distant servers in the fediverse, which
// can have much laxer requirements than those of Forgejo. It is not intended to check for
// invalid, non-standard compliant domains.
//
// For instance, the following should work:
// @user.όνομαß_21__@subdomain1.subdomain2.example.tld:65536
// @42@42.example.tld
// @user@example.tld:99999 (presumed to be an impossible case)
// @-@-.tld (also impossible)
validFediverseUsernamePattern = regexp.MustCompile(`^(@[\p{L}\p{M}0-9_\.\-]{1,})(@[\p{L}\p{M}0-9_\.\-]{1,})(:[1-9][0-9]{0,4})?$`)
)
// IsValidUsername checks if username is valid
func IsValidUsername(name string) bool {
// It is difficult to find a single pattern that is both readable and effective,
// but it's easier to use positive and negative checks.
if setting.Service.AllowDotsInUsernames {
return validUsernamePatternWithDots.MatchString(name) && !invalidUsernamePattern.MatchString(name)
}
return validUsernamePatternWithoutDots.MatchString(name) && !invalidUsernamePattern.MatchString(name)
}
// IsValidActivityPubUsername checks whether the username can be a valid ActivityPub handle.
//
// Username refers to the Forgejo user account's username for consistency, and not
// e.g. "username" in @username@example.tld.
func IsValidActivityPubUsername(name string) bool {
return validFediverseUsernamePattern.MatchString(name)
}