package backend import ( "fmt" "strings" ) // parseEnvFile parses KEY=VALUE lines in pure Go. Env files are NEVER // sourced or eval'd (house rule: no exec of env files); there is no // shell expansion, no command substitution, no interpolation of any // kind. Accepted syntax per line: // // # comment skipped (also inline comments after whitespace) // (blank line) skipped // export KEY=VALUE optional "export " prefix, skipped // KEY=VALUE KEY is [A-Za-z_][A-Za-z0-9_]*; VALUE is the rest // of the line, trimmed; one matched pair of // surrounding single or double quotes is stripped // (no escape processing) // // A later duplicate KEY overrides an earlier one (same order semantics // as sourcing the file). Malformed lines fail loudly: the error carries // the line NUMBER only, never the line contents (contents may be // material). func parseEnvFile(data []byte) (map[string]string, error) { out := map[string]string{} for i, line := range strings.Split(string(data), "\n") { line = strings.TrimRight(line, "\r") trimmed := strings.TrimSpace(line) if trimmed == "" || strings.HasPrefix(trimmed, "#") { continue } line = trimmed if strings.HasPrefix(line, "export ") || strings.HasPrefix(line, "export\t") { line = strings.TrimSpace(line[len("export"):]) } eq := strings.IndexByte(line, '=') if eq <= 0 { return nil, fmt.Errorf("line %d: malformed KEY=VALUE line", i+1) } key := strings.TrimSpace(line[:eq]) if !validEnvKey(key) { return nil, fmt.Errorf("line %d: malformed KEY=VALUE line", i+1) } value := strings.TrimSpace(line[eq+1:]) if idx := commentIndex(value); idx >= 0 { value = strings.TrimSpace(value[:idx]) } out[key] = unquote(value) } return out, nil } // validEnvKey reports whether key is a legal env-file identifier. func validEnvKey(key string) bool { if key == "" { return false } for i := 0; i < len(key); i++ { c := key[i] switch { case c >= 'a' && c <= 'z', c >= 'A' && c <= 'Z', c == '_': case c >= '0' && c <= '9': if i == 0 { return false } default: return false } } return true } // commentIndex finds an inline comment start (a # preceded by whitespace) // OUTSIDE a quoted value; -1 if none. func commentIndex(value string) int { var quote byte for i := 0; i < len(value); i++ { c := value[i] switch { case quote != 0: if c == quote { quote = 0 } case c == '"' || c == '\'': quote = c case c == '#' && (i == 0 || value[i-1] == ' ' || value[i-1] == '\t'): return i } } return -1 } // unquote strips one matched pair of surrounding quotes. func unquote(v string) string { if len(v) >= 2 { if (v[0] == '"' && v[len(v)-1] == '"') || (v[0] == '\'' && v[len(v)-1] == '\'') { return v[1 : len(v)-1] } } return v }