package main import ( "encoding/json" "errors" "fmt" "net/url" "os" "path/filepath" "strings" "time" ) type configFile struct { Host string `json:"host,omitempty"` Session string `json:"session,omitempty"` ExpiresAt time.Time `json:"expiresAt,omitempty"` } func defaultConfigPath() string { if value := os.Getenv("UFILE_CONFIG"); value != "" { return value } dir, err := os.UserConfigDir() if err != nil { return "ufile-cli.json" } return filepath.Join(dir, "ufile-cli", "config.json") } func loadConfig(path string) (configFile, error) { data, err := os.ReadFile(path) if errors.Is(err, os.ErrNotExist) { return configFile{}, nil } if err != nil { return configFile{}, err } var config configFile if err := json.Unmarshal(data, &config); err != nil { return configFile{}, fmt.Errorf("配置文件格式错误: %w", err) } return config, nil } func saveConfig(path string, config configFile) error { dir := filepath.Dir(path) if err := os.MkdirAll(dir, 0o700); err != nil { return err } data, err := json.MarshalIndent(config, "", " ") if err != nil { return err } data = append(data, '\n') temp, err := os.CreateTemp(dir, ".config-*") if err != nil { return err } tempName := temp.Name() defer os.Remove(tempName) if err := temp.Chmod(0o600); err != nil { temp.Close() return err } if _, err := temp.Write(data); err != nil { temp.Close() return err } if err := temp.Close(); err != nil { return err } if err := os.Rename(tempName, path); err == nil { return nil } if err := os.Remove(path); err != nil && !errors.Is(err, os.ErrNotExist) { return err } return os.Rename(tempName, path) } func normalizeHost(value string) (string, error) { value = strings.TrimSpace(value) parsed, err := url.Parse(value) if err != nil || parsed.Scheme == "" || parsed.Host == "" { return "", fmt.Errorf("无效的服务地址 %q,需要包含 http:// 或 https://", value) } if parsed.Scheme != "http" && parsed.Scheme != "https" { return "", fmt.Errorf("服务地址只支持 http 或 https") } if parsed.RawQuery != "" || parsed.Fragment != "" { return "", fmt.Errorf("服务地址不能包含查询参数或片段") } parsed.Path = strings.TrimRight(parsed.Path, "/") return strings.TrimRight(parsed.String(), "/"), nil }