package config import ( "errors" "net" "path/filepath" "strconv" "strings" "time" ) const ( HTTPAddressEnvironment = "CMROUBAO_HTTP_ADDR" DatabasePathEnvironment = "CMROUBAO_DATABASE_PATH" defaultHTTPAddress = "127.0.0.1:8080" defaultDatabasePath = "var/cmroubao.db" ) type LookupEnvironment func(string) (string, bool) type Config struct { HTTPAddress string DatabasePath string ReadHeaderTimeout time.Duration ReadTimeout time.Duration WriteTimeout time.Duration IdleTimeout time.Duration ShutdownTimeout time.Duration MaxHeaderBytes int } func Load(lookup LookupEnvironment) (Config, error) { httpAddress, err := environmentValue( lookup, HTTPAddressEnvironment, defaultHTTPAddress, ) if err != nil { return Config{}, err } if err := validateHTTPAddress(httpAddress); err != nil { return Config{}, err } databasePath, err := environmentValue( lookup, DatabasePathEnvironment, defaultDatabasePath, ) databasePath, err = validatedDatabasePath(databasePath, err) if err != nil { return Config{}, err } return Config{ HTTPAddress: httpAddress, DatabasePath: filepath.Clean(databasePath), ReadHeaderTimeout: 5 * time.Second, ReadTimeout: 15 * time.Second, WriteTimeout: 30 * time.Second, IdleTimeout: 60 * time.Second, ShutdownTimeout: 10 * time.Second, MaxHeaderBytes: 1 << 20, }, nil } func LoadDatabasePath(lookup LookupEnvironment) (string, error) { databasePath, err := environmentValue( lookup, DatabasePathEnvironment, defaultDatabasePath, ) return validatedDatabasePath(databasePath, err) } func environmentValue( lookup LookupEnvironment, name string, defaultValue string, ) (string, error) { value, exists := lookup(name) if !exists { return defaultValue, nil } value = strings.TrimSpace(value) if value == "" { return "", errors.New(name + " must not be blank") } return value, nil } func validateHTTPAddress(address string) error { host, portValue, err := net.SplitHostPort(address) if err != nil || strings.TrimSpace(host) == "" { return errors.New(HTTPAddressEnvironment + " must include a host and port") } port, err := strconv.Atoi(portValue) if err != nil || port < 1 || port > 65535 { return errors.New(HTTPAddressEnvironment + " port must be between 1 and 65535") } return nil } func validatedDatabasePath(path string, previousError error) (string, error) { if previousError != nil { return "", previousError } if strings.ContainsRune(path, '\x00') { return "", errors.New( DatabasePathEnvironment + " contains an invalid character", ) } lowerPath := strings.ToLower(path) cleanPath := filepath.Clean(path) extension := strings.ToLower(filepath.Ext(cleanPath)) if cleanPath == "." || cleanPath == string(filepath.Separator) || lowerPath == ":memory:" || strings.HasPrefix(lowerPath, "file:") || (extension != ".db" && extension != ".sqlite" && extension != ".sqlite3") { return "", errors.New( DatabasePathEnvironment + " must be a SQLite file path", ) } return cleanPath, nil }