feat: site languages, setup and site URL settings

This commit is contained in:
Florian Sylvain committed 2026-11-01 22:13:02 +01:00
1 parent 0806733d5c
commit d9158025d4
106 files changed
+10184 -56

No files matched your search

@@ -0,0 +1,117 @@
package site
import (
"net/url"
"strings"
"unicode/utf8"
)
const (
MaxPublicURLPatternRunes = 512
LangPlaceholder = "{lang}"
SlugPlaceholder = "{slug}"
placeholderFiller = "x"
allowedURLSymbols = "-._~:/?#[]@!$&'()*+,;=%"
)
func NormalizePublicURLPattern(pattern *string) (*string, error) {
if pattern == nil {
return nil, nil
}
trimmed := strings.TrimSpace(*pattern)
if trimmed == "" {
return nil, nil
}
if utf8.RuneCountInString(trimmed) > MaxPublicURLPatternRunes {
return nil, ErrInvalidPublicURLPattern
}
if err := checkPublicURLScheme(trimmed); err != nil {
return nil, err
}
if err := checkPlaceholders(trimmed); err != nil {
return nil, err
}
filled := fillPlaceholders(trimmed)
if err := checkEncoding(filled); err != nil {
return nil, err
}
if !strings.Contains(trimmed, SlugPlaceholder) {
return nil, ErrPublicURLMissingSlug
}
if err := checkPublicURLHost(filled); err != nil {
return nil, err
}
return &trimmed, nil
}
func checkPublicURLScheme(pattern string) error {
lowered := strings.ToLower(pattern)
if !strings.HasPrefix(lowered, "https://") && !strings.HasPrefix(lowered, "http://") {
return ErrPublicURLScheme
}
return nil
}
func checkPlaceholders(pattern string) error {
remaining := pattern
for {
open := strings.IndexAny(remaining, "{}")
if open < 0 {
return nil
}
if remaining[open] == '}' {
return ErrPublicURLPlaceholder
}
rest := remaining[open:]
switch {
case strings.HasPrefix(rest, LangPlaceholder):
remaining = rest[len(LangPlaceholder):]
case strings.HasPrefix(rest, SlugPlaceholder):
remaining = rest[len(SlugPlaceholder):]
default:
return ErrPublicURLPlaceholder
}
}
}
func fillPlaceholders(pattern string) string {
return strings.NewReplacer(LangPlaceholder, placeholderFiller, SlugPlaceholder, placeholderFiller).Replace(pattern)
}
func checkEncoding(filled string) error {
for index := 0; index < len(filled); index++ {
character := filled[index]
if character == '%' {
if index+2 >= len(filled) || !isHex(filled[index+1]) || !isHex(filled[index+2]) {
return ErrPublicURLNotEncoded
}
index += 2
continue
}
if !isAllowedURLByte(character) {
return ErrPublicURLNotEncoded
}
}
return nil
}
func isHex(character byte) bool {
return character >= '0' && character <= '9' || character >= 'a' && character <= 'f' || character >= 'A' && character <= 'F'
}
func isAllowedURLByte(character byte) bool {
if character >= '0' && character <= '9' || character >= 'a' && character <= 'z' || character >= 'A' && character <= 'Z' {
return true
}
return strings.IndexByte(allowedURLSymbols, character) >= 0
}
func checkPublicURLHost(filled string) error {
parsed, err := url.Parse(filled)
if err != nil || parsed.Hostname() == "" {
return ErrPublicURLHost
}
return nil
}