mirror of
https://github.com/lukaszraczylo/lolcathost.git
synced 2026-06-11 00:08:57 +00:00
Initial commit.
This commit is contained in:
@@ -0,0 +1,211 @@
|
||||
// Package config provides validation functions for configuration.
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// domainRegex validates domain names.
|
||||
var domainRegex = regexp.MustCompile(`^(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$|^localhost$`)
|
||||
|
||||
// aliasRegex validates alias names.
|
||||
var aliasRegex = regexp.MustCompile(`^[a-zA-Z0-9][a-zA-Z0-9_-]{0,62}$`)
|
||||
|
||||
// blockedDomains contains domains that cannot be modified.
|
||||
var blockedDomains = map[string]bool{
|
||||
"apple.com": true,
|
||||
"icloud.com": true,
|
||||
"icloud-content.com": true,
|
||||
"apple-dns.cn": true,
|
||||
"apple-dns.net": true,
|
||||
"mzstatic.com": true,
|
||||
"itunes.apple.com": true,
|
||||
"updates.apple.com": true,
|
||||
}
|
||||
|
||||
// ValidationError represents a configuration validation error.
|
||||
type ValidationError struct {
|
||||
Field string
|
||||
Message string
|
||||
}
|
||||
|
||||
func (e *ValidationError) Error() string {
|
||||
return fmt.Sprintf("%s: %s", e.Field, e.Message)
|
||||
}
|
||||
|
||||
// ValidateConfig validates the entire configuration.
|
||||
func ValidateConfig(cfg *Config) error {
|
||||
if cfg == nil {
|
||||
return &ValidationError{Field: "config", Message: "config is nil"}
|
||||
}
|
||||
|
||||
if err := validateSettings(&cfg.Settings); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Track aliases for uniqueness
|
||||
aliases := make(map[string]bool)
|
||||
|
||||
for i, g := range cfg.Groups {
|
||||
if err := validateGroup(&g, i, aliases); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
for i, p := range cfg.Presets {
|
||||
if err := validatePreset(&p, i, aliases); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateSettings(s *Settings) error {
|
||||
switch s.FlushMethod {
|
||||
case FlushMethodAuto, FlushMethodDscacheutil, FlushMethodKillall, FlushMethodBoth, "":
|
||||
// Valid
|
||||
default:
|
||||
return &ValidationError{
|
||||
Field: "settings.flushMethod",
|
||||
Message: fmt.Sprintf("invalid flush method: %s", s.FlushMethod),
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateGroup(g *Group, index int, aliases map[string]bool) error {
|
||||
if strings.TrimSpace(g.Name) == "" {
|
||||
return &ValidationError{
|
||||
Field: fmt.Sprintf("groups[%d].name", index),
|
||||
Message: "group name is required",
|
||||
}
|
||||
}
|
||||
|
||||
for i, h := range g.Hosts {
|
||||
if err := validateHost(&h, index, i, aliases); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateHost(h *Host, groupIndex, hostIndex int, aliases map[string]bool) error {
|
||||
fieldPrefix := fmt.Sprintf("groups[%d].hosts[%d]", groupIndex, hostIndex)
|
||||
|
||||
// Validate domain
|
||||
if !ValidateDomain(h.Domain) {
|
||||
return &ValidationError{
|
||||
Field: fieldPrefix + ".domain",
|
||||
Message: fmt.Sprintf("invalid domain: %s", h.Domain),
|
||||
}
|
||||
}
|
||||
|
||||
// Check blocked domains
|
||||
if IsBlockedDomain(h.Domain) {
|
||||
return &ValidationError{
|
||||
Field: fieldPrefix + ".domain",
|
||||
Message: fmt.Sprintf("domain is blocked: %s", h.Domain),
|
||||
}
|
||||
}
|
||||
|
||||
// Validate IP
|
||||
if !ValidateIP(h.IP) {
|
||||
return &ValidationError{
|
||||
Field: fieldPrefix + ".ip",
|
||||
Message: fmt.Sprintf("invalid IP address: %s", h.IP),
|
||||
}
|
||||
}
|
||||
|
||||
// Validate alias
|
||||
if !ValidateAlias(h.Alias) {
|
||||
return &ValidationError{
|
||||
Field: fieldPrefix + ".alias",
|
||||
Message: fmt.Sprintf("invalid alias: %s", h.Alias),
|
||||
}
|
||||
}
|
||||
|
||||
// Check alias uniqueness
|
||||
if aliases[h.Alias] {
|
||||
return &ValidationError{
|
||||
Field: fieldPrefix + ".alias",
|
||||
Message: fmt.Sprintf("duplicate alias: %s", h.Alias),
|
||||
}
|
||||
}
|
||||
aliases[h.Alias] = true
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func validatePreset(p *Preset, index int, aliases map[string]bool) error {
|
||||
fieldPrefix := fmt.Sprintf("presets[%d]", index)
|
||||
|
||||
if strings.TrimSpace(p.Name) == "" {
|
||||
return &ValidationError{
|
||||
Field: fieldPrefix + ".name",
|
||||
Message: "preset name is required",
|
||||
}
|
||||
}
|
||||
|
||||
// Note: We don't validate preset aliases strictly anymore.
|
||||
// Unknown aliases in presets will simply be skipped when applying the preset.
|
||||
// This allows presets to survive when hosts are removed from the config.
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ValidateDomain checks if a domain name is valid.
|
||||
func ValidateDomain(domain string) bool {
|
||||
if domain == "" {
|
||||
return false
|
||||
}
|
||||
return domainRegex.MatchString(domain)
|
||||
}
|
||||
|
||||
// ValidateIP checks if an IP address is valid (IPv4 or IPv6).
|
||||
func ValidateIP(ip string) bool {
|
||||
if ip == "" {
|
||||
return false
|
||||
}
|
||||
return net.ParseIP(ip) != nil
|
||||
}
|
||||
|
||||
// ValidateAlias checks if an alias is valid.
|
||||
func ValidateAlias(alias string) bool {
|
||||
if alias == "" {
|
||||
return false
|
||||
}
|
||||
return aliasRegex.MatchString(alias)
|
||||
}
|
||||
|
||||
// IsBlockedDomain checks if a domain is in the blocklist.
|
||||
func IsBlockedDomain(domain string) bool {
|
||||
domain = strings.ToLower(domain)
|
||||
|
||||
// Check exact match
|
||||
if blockedDomains[domain] {
|
||||
return true
|
||||
}
|
||||
|
||||
// Check if it's a subdomain of a blocked domain
|
||||
for blocked := range blockedDomains {
|
||||
if strings.HasSuffix(domain, "."+blocked) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// GetBlockedDomains returns a copy of the blocked domains list.
|
||||
func GetBlockedDomains() []string {
|
||||
domains := make([]string, 0, len(blockedDomains))
|
||||
for d := range blockedDomains {
|
||||
domains = append(domains, d)
|
||||
}
|
||||
return domains
|
||||
}
|
||||
Reference in New Issue
Block a user