feat: site languages, setup and site URL settings
This commit is contained in:
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
|
||||
}
|
||||
Reference in new issue
Block a user