radchat/utils.go

113 lines
2.6 KiB
Go

package main
// Utilities for filesystem, IDs, HTTP scheme detection, and input sanitization.
// These helpers are used by the HTTP handlers and startup code.
import (
"crypto/sha256"
"encoding/hex"
"fmt"
"net/http"
"os"
"path/filepath"
"strings"
"time"
)
// MakeDir creates the directory if it does not already exist.
func MakeDir(dirname string) error {
if _, err := os.Stat(dirname); os.IsNotExist(err) {
return os.Mkdir(dirname, 0666)
}
return nil
}
// DeleteDirContents removes all files and directories inside dirname but not the directory itself.
func DeleteDirContents(dirname string) error {
entries, err := os.ReadDir(dirname)
if err != nil {
return err
}
for _, entry := range entries {
if entry.IsDir() {
err := os.RemoveAll(filepath.Join(dirname, entry.Name()))
if err != nil {
return err
}
} else {
err := os.Remove(filepath.Join(dirname, entry.Name()))
if err != nil {
return err
}
}
}
return nil
}
// IsUsernameValid validates length, allowed characters, and rejects common placeholder names.
func IsUsernameValid(username string) bool {
minLength := 3
maxLength := 24
allowedChars := "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-"
disallowedUsernames := []string{
"admin",
"user",
"guest",
"test",
"root",
"system",
"anonymous",
"default",
}
if len(username) < minLength || len(username) > maxLength {
return false
}
for _, char := range username {
if !strings.ContainsRune(allowedChars, char) {
return false
}
}
for _, disallowed := range disallowedUsernames {
if strings.EqualFold(username, disallowed) {
return false
}
}
return true
}
// GenerateId creates a short pseudo-unique ID derived from time and a hash.
func GenerateId() string {
timestamp := time.Now().UnixNano()
randomComponent := time.Now().UnixNano() % 1000000 // Add some randomness
data := fmt.Sprintf("%d-%d", timestamp, randomComponent)
hash := sha256.Sum256([]byte(data))
return hex.EncodeToString(hash[:])[:16]
}
// GetScheme determines the request scheme, respecting TLS and X-Forwarded-Proto.
func GetScheme(r *http.Request) string {
if r.URL.Scheme != "" {
return r.URL.Scheme
}
if r.TLS != nil {
return "https"
}
proto := r.Header.Get("X-Forwarded-Proto")
if proto != "" {
return proto
}
return "http"
}
// SanitizeFilename removes path traversal characters and trims whitespace.
func SanitizeFilename(filename string) string {
filename = strings.ReplaceAll(filename, "/", "")
filename = strings.ReplaceAll(filename, "\\", "")
filename = strings.ReplaceAll(filename, "..", "")
filename = strings.TrimSpace(filename)
return filename
}