package config import ( "bufio" "fmt" "os" "strconv" "strings" ) type AccountConfig struct { Username string Password string Role string Default string None []string Read []string Write []string Delete []string } type Config struct { Path []string Port int Allow string TrustedProxies string Rules []string Log string FileRemove int ImagePreview bool FolderDownload string SessionTimeout int JWTSecret string HTMLTitle string HTMLNotice string UserFile string UserHome string ThumbnailMaxSize int64 ThumbnailMaxPixels int64 ZipMaxSize int64 ZipMaxFiles int UploadMaxSize int64 UploadMaxTotalSize int64 UploadMaxFiles int SearchMaxResults int SSLCert string SSLKey string Accounts []AccountConfig } func Default() Config { return Config{Path: []string{"."}, Port: 80, FileRemove: 1, ImagePreview: true, FolderDownload: "enable", SessionTimeout: 30, UserHome: "users", ThumbnailMaxSize: 32 << 20, ThumbnailMaxPixels: 40_000_000, ZipMaxSize: 2 << 30, ZipMaxFiles: 5000, UploadMaxSize: 2 << 30, UploadMaxTotalSize: 4 << 30, UploadMaxFiles: 100, SearchMaxResults: 500} } func Load(path string) (Config, error) { c := Default() f, err := os.Open(path) if err != nil { return c, err } defer f.Close() s := bufio.NewScanner(f) var section *AccountConfig for s.Scan() { line := strings.TrimSpace(s.Text()) if line == "" || strings.HasPrefix(line, ";") || strings.HasPrefix(line, "#") { continue } if strings.HasPrefix(line, "[") && strings.HasSuffix(line, "]") { name := strings.TrimSpace(line[1 : len(line)-1]) if name != "" { c.Accounts = append(c.Accounts, AccountConfig{Username: name}) section = &c.Accounts[len(c.Accounts)-1] } else { section = nil } continue } i := strings.IndexByte(line, '=') if i < 0 { continue } key, val := strings.TrimSpace(line[:i]), strings.TrimSpace(line[i+1:]) val = strings.Trim(val, "\\\"") if section != nil { switch strings.ToLower(key) { case "password": section.Password = val case "role": section.Role = strings.ToLower(val) case "rule.default": section.Default = val case "rule.none": section.None = appendNonEmpty(section.None, val) case "rule.r": section.Read = appendNonEmpty(section.Read, val) case "rule.w": section.Write = appendNonEmpty(section.Write, val) case "rule.d": section.Delete = appendNonEmpty(section.Delete, val) } continue } switch strings.ToLower(key) { case "path": c.Path = splitPath(val) case "port": if n, e := strconv.Atoi(val); e == nil && n > 0 && n < 65536 { c.Port = n } case "allow": c.Allow = val case "trusted-proxies": c.TrustedProxies = val case "rule": if val != "" { c.Rules = append(c.Rules, val) } case "log": c.Log = val case "file.remove": if n, e := strconv.Atoi(val); e == nil { c.FileRemove = n } case "image.preview": c.ImagePreview = parseBool(val, c.ImagePreview) case "folder.download": c.FolderDownload = strings.ToLower(val) case "folder.leaf.download": if parseBool(val, false) { c.FolderDownload = "leaf" } case "session.timeout": if n, e := strconv.Atoi(val); e == nil && n > 0 { c.SessionTimeout = n } case "jwt.secret": c.JWTSecret = val case "html.title": c.HTMLTitle = val case "html.notice": c.HTMLNotice = val case "user.file": c.UserFile = val case "user.home": c.UserHome = strings.Trim(val, "/") case "thumbnail.max-size": if n, e := strconv.ParseInt(val, 10, 64); e == nil && n >= 0 { c.ThumbnailMaxSize = n } case "thumbnail.max-pixels": if n, e := strconv.ParseInt(val, 10, 64); e == nil && n >= 0 { c.ThumbnailMaxPixels = n } case "zip.max-size": if n, e := strconv.ParseInt(val, 10, 64); e == nil && n >= 0 { c.ZipMaxSize = n } case "zip.max-files": if n, e := strconv.Atoi(val); e == nil && n >= 0 { c.ZipMaxFiles = n } case "upload.max-size": if n, e := strconv.ParseInt(val, 10, 64); e == nil && n >= 0 { c.UploadMaxSize = n } case "upload.max-total-size": if n, e := strconv.ParseInt(val, 10, 64); e == nil && n >= 0 { c.UploadMaxTotalSize = n } case "upload.max-files": if n, e := strconv.Atoi(val); e == nil && n >= 0 { c.UploadMaxFiles = n } case "search.max-results": if n, e := strconv.Atoi(val); e == nil && n >= 0 { c.SearchMaxResults = n } case "ssl.cert": c.SSLCert = val case "ssl.key": c.SSLKey = val } } if err := s.Err(); err != nil { return c, err } return c, nil } func splitPath(v string) []string { var out []string for _, p := range strings.Split(v, "|") { p = strings.TrimSpace(strings.Trim(p, "\\\"")) if p != "" { out = append(out, p) } } if len(out) == 0 { return []string{"."} } return out } func parseBool(v string, d bool) bool { switch strings.ToLower(strings.TrimSpace(v)) { case "true", "1", "yes", "on": return true case "false", "0", "no", "off": return false } return d } func (c Config) Validate() error { if len(c.Path) == 0 { return fmt.Errorf("path is empty") } if c.Port < 1 || c.Port > 65535 { return fmt.Errorf("invalid port") } return nil } func appendNonEmpty(dst []string, value string) []string { for _, item := range strings.Split(value, "|") { item = strings.TrimSpace(item) if item != "" { dst = append(dst, item) } } return dst }